merge from trunk

This commit is contained in:
ldolse 2010-09-21 18:29:52 -10:00
commit fbd7814269
30 changed files with 1351 additions and 120 deletions

688
imgsrc/trim.svg Normal file
View File

@ -0,0 +1,688 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="128"
height="128"
id="svg1307"
sodipodi:version="0.32"
inkscape:version="0.46+devel"
version="1.0"
sodipodi:docname="transform-crop.svgz"
inkscape:export-filename="/home/pinheiro/pics/oxygen-icons/scalable/actions/transform-crop.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
inkscape:output_extension="org.inkscape.output.svgz.inkscape">
<defs
id="defs1309">
<linearGradient
inkscape:collect="always"
id="linearGradient2594">
<stop
style="stop-color:#fafafa;stop-opacity:1;"
offset="0"
id="stop2596" />
<stop
style="stop-color:#fafafa;stop-opacity:0;"
offset="1"
id="stop2598" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient3969">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop3971" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop3973" />
</linearGradient>
<linearGradient
id="linearGradient2783">
<stop
style="stop-color:#323232;stop-opacity:1;"
offset="0"
id="stop2785" />
<stop
id="stop2787"
offset="0.07692308"
style="stop-color:#dfe1e1;stop-opacity:1;" />
<stop
style="stop-color:#b6b1b1;stop-opacity:1;"
offset="0.26289096"
id="stop2799" />
<stop
id="stop2789"
offset="0.5"
style="stop-color:#8d8282;stop-opacity:1;" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0.78201604"
id="stop2791" />
<stop
style="stop-color:#dfd9df;stop-opacity:1;"
offset="0.9005897"
id="stop2793" />
<stop
style="stop-color:#3a3a3a;stop-opacity:1;"
offset="1"
id="stop2795" />
</linearGradient>
<linearGradient
id="linearGradient2222"
inkscape:collect="always">
<stop
id="stop2224"
offset="0"
style="stop-color:#0066ff;stop-opacity:1" />
<stop
id="stop2226"
offset="1"
style="stop-color:#80b3ff;stop-opacity:1" />
</linearGradient>
<linearGradient
id="linearGradient3314"
inkscape:collect="always">
<stop
id="stop3316"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop3318"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient2431">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop2433" />
<stop
id="stop2435"
offset="0.42597079"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop2437"
offset="0.5892781"
style="stop-color:#f1f1f1;stop-opacity:1;" />
<stop
style="stop-color:#eaeaea;stop-opacity:1;"
offset="0.80219781"
id="stop2439" />
<stop
style="stop-color:#dfdfdf;stop-opacity:1;"
offset="1"
id="stop2441" />
</linearGradient>
<linearGradient
id="linearGradient7422">
<stop
style="stop-color:#b4b4b6;stop-opacity:1;"
offset="0"
id="stop7424" />
<stop
id="stop5348"
offset="0.5"
style="stop-color:#9c9ca1;stop-opacity:1;" />
<stop
id="stop7426"
offset="1"
style="stop-color:#cdcdd1;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient3310"
inkscape:collect="always">
<stop
id="stop3312"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop3314"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<filter
inkscape:collect="always"
x="-0.21138181"
width="1.4227636"
y="-0.21047288"
height="1.4209458"
id="filter9723">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="1.4336041"
id="feGaussianBlur9725" />
</filter>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath10698">
<path
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1"
d="M -128.2008,-3.392377 L -104.45558,6.3360672 L -102.43766,6.1757677 L -103.81912,-4.5678172 L -105.75454,-5.8316609 L -124.96922,-4.4459394 L -128.2008,-3.392377 z "
id="path10700"
sodipodi:nodetypes="ccccccc" />
</clipPath>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2783"
id="radialGradient3418"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.9728905,-8.15107,-18.526373,-2.211261,1957.2342,725.31677)"
cx="53.235302"
cy="106.0573"
fx="53.235302"
fy="106.0573"
r="9.1025209" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient2594"
id="radialGradient3420"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.5808473,-2.8009276,-6.4965168,-1.3472267,701.00301,348.75795)"
cx="53.347126"
cy="104.68401"
fx="53.347126"
fy="104.68401"
r="9.1025209" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient3314"
id="radialGradient3422"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-2.9339535,-1.0170467,-1.1904108,3.4340702,323.071,-252.78281)"
cx="49.110855"
cy="105.43803"
fx="49.110855"
fy="105.43803"
r="10.20672" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2783"
id="linearGradient3425"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.2608955,0,0,1.9345479,-550.58555,-317.90247)"
x1="190.03462"
y1="90.22673"
x2="208.7153"
y2="90.22673" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3969"
id="linearGradient3430"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.2608955,0,0,1.9345479,-497.11778,-432.24104)"
x1="98.411324"
y1="185.68851"
x2="166.32983"
y2="155.59846" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7422"
id="linearGradient3525"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(6.0715756e-2,0,0,9.7589526e-2,24.201706,-45.627655)"
x1="399.77466"
y1="1164.6696"
x2="399.77466"
y2="549.06134" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2431"
id="linearGradient3527"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.5415355,0,0,0.7222225,23.477667,-8.2222193)"
x1="119.57646"
y1="23.792561"
x2="15.999996"
y2="109.6508" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3310"
id="linearGradient3529"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0,-1.5975038,-2,0,96,199.26848)"
x1="102.31124"
y1="-5.8302126"
x2="74.330322"
y2="32" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2222"
id="linearGradient3538"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.7476489,0,0,0.7476489,0,-19.999999)"
x1="8.2386189"
y1="-13.864992"
x2="8.2386189"
y2="-1.4047648" />
<filter
inkscape:collect="always"
id="filter4420">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="3.0486726"
id="feGaussianBlur4422" />
</filter>
<mask
maskUnits="userSpaceOnUse"
id="mask3562">
<rect
ry="1.4444447"
rx="1.1997639"
y="8"
x="-4.0000005"
height="116.00001"
width="124"
id="rect3564"
style="fill:#ffffff;fill-opacity:1;stroke:none;filter:url(#filter4420)"
transform="matrix(1.1453342,0,0,1.1453342,15.087799,-38.432604)" />
</mask>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.2136483"
inkscape:cx="77.317692"
inkscape:cy="55.850409"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:document-units="px"
inkscape:grid-bbox="true"
guidetolerance="4"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1440"
inkscape:window-height="840"
inkscape:window-x="223"
inkscape:window-y="37"
objecttolerance="4"
gridtolerance="4">
<sodipodi:guide
orientation="horizontal"
position="-32.073749"
id="guide2204" />
<inkscape:grid
id="GridFromPre046Settings"
type="xygrid"
originx="0px"
originy="0px"
spacingx="4px"
spacingy="4px"
color="#0000ff"
empcolor="#0000ff"
opacity="0.2"
empopacity="0.4"
empspacing="4"
visible="true"
enabled="true" />
</sodipodi:namedview>
<metadata
id="metadata1312">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<cc:license
rdf:resource="http://creativecommons.org/licenses/GPL/2.0/" />
<dc:contributor>
<cc:Agent>
<dc:title>Oxygen team</dc:title>
</cc:Agent>
</dc:contributor>
<dc:title></dc:title>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/LGPL/2.1/">
<cc:permits
rdf:resource="http://web.resource.org/cc/Reproduction" />
<cc:permits
rdf:resource="http://web.resource.org/cc/Distribution" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Notice" />
<cc:permits
rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
<cc:requires
rdf:resource="http://web.resource.org/cc/ShareAlike" />
<cc:requires
rdf:resource="http://web.resource.org/cc/SourceCode" />
</cc:License>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<rect
ry="0.1870501"
rx="0.1537565"
y="28.129654"
x="8"
height="92"
width="92"
id="rect3226"
style="fill:#618fd2;fill-opacity:0.09195401;stroke:none;stroke-width:0.86699998;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.08779998;stroke-opacity:1" />
<g
id="g3520"
transform="translate(32,-0.1296539)">
<rect
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="/home/pinheiro/Desktop/mock2.png"
style="opacity:0.75;fill:url(#linearGradient3525);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="rect3281"
width="92"
height="92"
x="28.129654"
y="-24"
inkscape:r_cx="true"
inkscape:r_cy="true"
ry="3.9616783"
rx="3.9616783"
transform="matrix(0,1,1,0,0,0)" />
<rect
ry="1.4444447"
rx="1.1997639"
y="-20"
x="32.129654"
height="84"
width="84"
id="rect3283"
style="fill:url(#linearGradient3527);fill-opacity:1;fill-rule:evenodd;stroke:none"
transform="matrix(0,1,1,0,0,0)" />
<path
id="path3285"
d="M 64,53.096891 C 45.143834,70.163928 24.748768,86.162699 -2.0000002e-07,96.129654 L -2.0000002e-07,52.647595 C 23.693959,50.212248 45.09831,42.609775 64,32.129654 L 64,53.096891 z"
style="fill:url(#linearGradient3529);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<g
transform="translate(-16,20.129654)"
style="fill:#7193c6;fill-opacity:1"
id="g2250">
<rect
ry="1.3512546"
rx="0.077153668"
y="-116"
x="16"
height="4"
width="4"
id="rect3210"
style="opacity:1;fill:#7193c6;fill-opacity:1;stroke:none;stroke-width:0.86699998;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.08779998;stroke-opacity:1"
transform="matrix(0,1,-1,0,0,0)"
inkscape:tile-w="8"
inkscape:tile-h="8"
inkscape:tile-cx="124"
inkscape:tile-cy="28" />
<use
style="fill:#7193c6;fill-opacity:1"
x="0"
y="0"
inkscape:tiled-clone-of="#rect3210"
xlink:href="#rect3210"
transform="translate(0,8)"
id="use2236"
width="128"
height="128" />
<use
style="fill:#7193c6;fill-opacity:1"
x="0"
y="0"
inkscape:tiled-clone-of="#rect3210"
xlink:href="#rect3210"
transform="translate(0,16)"
id="use2240"
width="128"
height="128" />
<use
style="fill:#7193c6;fill-opacity:1"
x="0"
y="0"
inkscape:tiled-clone-of="#rect3210"
xlink:href="#rect3210"
transform="translate(0,24)"
id="use2244"
width="128"
height="128" />
<use
style="fill:#7193c6;fill-opacity:1"
x="0"
y="0"
inkscape:tiled-clone-of="#rect3210"
xlink:href="#rect3210"
transform="translate(0,32)"
id="use2248"
width="128"
height="128" />
<use
height="88"
width="88"
transform="translate(0,24)"
id="use3220"
xlink:href="#use2240"
y="0"
x="0" />
<use
height="88"
width="88"
transform="translate(0,24)"
id="use3222"
xlink:href="#use2244"
y="0"
x="0" />
<use
height="128"
width="128"
transform="translate(0,32)"
id="use2230"
xlink:href="#use2244"
y="0"
x="0" />
<use
height="128"
width="128"
transform="translate(0,32)"
id="use2232"
xlink:href="#use2248"
y="0"
x="0" />
<use
height="128"
width="128"
transform="translate(0,32)"
id="use2234"
xlink:href="#use3220"
y="0"
x="0" />
</g>
<use
height="128"
width="128"
transform="matrix(8.5712909e-8,-0.9999999,0.9999999,8.5712909e-8,-20.129659,128.12964)"
id="use2258"
xlink:href="#g2250"
y="0"
x="0" />
<use
height="128"
width="128"
transform="translate(-88,0)"
id="use2314"
xlink:href="#g2250"
y="0"
x="0" />
<use
height="128"
width="128"
transform="matrix(8.5712909e-8,-0.9999999,0.9999999,8.5712909e-8,-20.129651,216.12964)"
id="use2316"
xlink:href="#g2250"
y="0"
x="0" />
<use
height="128"
width="128"
transform="translate(96,0.1296547)"
id="use3300"
xlink:href="#rect3222"
y="0"
x="0" />
<use
height="128"
width="128"
transform="translate(7.4990672e-6,96.129662)"
id="use3302"
xlink:href="#rect3222"
y="0"
x="0" />
<use
height="128"
width="128"
transform="translate(96,96.129652)"
id="use3304"
xlink:href="#rect3222"
y="0"
x="0" />
<rect
ry="0.18696606"
rx="0.15479258"
y="-32"
x="0"
height="12"
width="12"
id="rect3222"
style="fill:url(#linearGradient3538);fill-opacity:1;stroke:none;stroke-width:0.86699998;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.08779998;stroke-opacity:1"
transform="scale(1,-1)" />
<rect
transform="scale(1,-1)"
style="fill:#bfd9ff;fill-opacity:1;stroke:none;stroke-width:0.86699998;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1.08779998;stroke-opacity:1"
id="rect2225"
width="4"
height="4"
x="4"
y="-28"
rx="0.15479258"
ry="0.18696606" />
<use
style="fill:#a4c0e4"
height="88"
width="88"
transform="translate(96,0.1296539)"
id="use3226"
xlink:href="#rect2225"
y="0"
x="0" />
<use
style="fill:#a4c0e4"
height="88"
width="88"
transform="translate(7.5e-6,96.129661)"
id="use3228"
xlink:href="#rect2225"
y="0"
x="0" />
<use
style="fill:#a4c0e4"
height="88"
width="88"
transform="translate(96,96.129654)"
id="use3230"
xlink:href="#rect2225"
y="0"
x="0" />
<rect
style="opacity:0.57786889;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.63199997;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1"
id="rect1327"
width="1"
height="0"
x="15.057414"
y="-308.20486" />
<g
id="g3407"
transform="matrix(0.8731076,0,0,0.8731076,-13.173272,33.555799)"
mask="url(#mask3562)">
<path
sodipodi:nodetypes="ccccccc"
id="path3836"
d="m 29.733826,93.557578 76.565594,-35.724313 3.74271,-5.050163 -27.964957,-18.69067 -6.907623,1.950856 -41.307066,47.80066 -4.128658,9.71363 z"
style="fill:url(#radialGradient3418);fill-opacity:1;fill-rule:nonzero;stroke:none" />
<path
style="fill:#555753;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 107.32508,50.938663 -74.427424,35.613119 -3.008197,6.986785 76.368201,-35.710168 3.7845,-5.046004 -2.71708,-1.843732 z"
id="path8241"
sodipodi:nodetypes="cccccc" />
<path
style="opacity:0.10688836;fill:url(#radialGradient3420);fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 29.733826,93.557578 76.565594,-35.724313 3.74271,-5.050163 -27.964957,-18.69067 -6.907623,1.950856 -41.307066,47.80066 -4.128658,9.71363 z"
id="path11683"
sodipodi:nodetypes="ccccccc" />
<path
sodipodi:nodetypes="ccccccc"
id="path17921"
d="m 29.733826,93.557578 76.565594,-35.724313 3.74271,-5.050163 -27.964957,-18.69067 -6.907623,1.950856 -41.307066,47.80066 -4.128658,9.71363 z"
style="fill:none;stroke:url(#radialGradient3422);stroke-width:0.86455041;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:4" />
<rect
style="fill:#2e3436;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="rect8239"
width="39.714981"
height="37.454777"
x="27.310663"
y="81.415123"
transform="matrix(0.6571695,-0.7537428,0.7537428,0.6571695,0,0)"
rx="3.8771732"
ry="3.8771732" />
<rect
transform="matrix(-0.7651682,-0.6438304,-0.6438304,0.7651682,0,0)"
style="fill:url(#linearGradient3425);fill-opacity:1;fill-rule:nonzero;stroke:none"
id="rect2803"
width="40.499767"
height="122.13765"
x="-120.93575"
y="-157.97318"
rx="0"
ry="0" />
<rect
transform="matrix(-0.7651682,-0.6438304,-0.6438304,0.7651682,0,0)"
y="-161.84383"
x="-119.89533"
height="126.00658"
width="39.223213"
id="rect3967"
style="fill:url(#linearGradient3430);fill-opacity:1;fill-rule:nonzero;stroke:none" />
<rect
transform="matrix(-0.6438304,0.7651682,0.7651682,0.6438304,0,0)"
y="80.243172"
x="-155.77248"
height="40.591759"
width="100.57008"
id="rect1851"
style="opacity:0.52459011;fill:#e0e0e0;fill-opacity:1;fill-rule:nonzero;stroke:none" />
<rect
ry="1.2485937"
rx="1.2485937"
transform="matrix(2.0406638,-2.3405465,2.3405465,2.0406638,304.62828,-199.57966)"
y="-5.487061"
x="-104.11894"
height="12.061829"
width="12.789698"
id="rect8248"
style="fill:#2e3436;fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter9723)"
clip-path="url(#clipPath10698)" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 21 KiB

BIN
resources/images/trim.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -49,7 +49,11 @@ class Danas(BasicNewsRecipe):
, 'language' : language , 'language' : language
} }
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] preprocess_regexps = [
(re.compile(u'\u0110'), lambda match: u'\u00D0')
,(re.compile(u'\u201c'), lambda match: '"')
,(re.compile(u'\u201e'), lambda match: '"')
]
keep_only_tags = [dict(name='div', attrs={'id':'left'})] keep_only_tags = [dict(name='div', attrs={'id':'left'})]
remove_tags = [ remove_tags = [

View File

@ -50,3 +50,5 @@ class Novosti(BasicNewsRecipe):
item['alt'] = 'image' item['alt'] = 'image'
return soup return soup

View File

@ -0,0 +1,79 @@
__license__ = 'GPL v3'
__copyright__ = '2010, Luciano Furtado <lrfurtado at yahoo.com.br>'
'''
www.superesportes.com.br
'''
from calibre.web.feeds.news import BasicNewsRecipe
class SuperEsportesRecipe(BasicNewsRecipe):
title = u'www.superesportes.com.br'
description = u'Superesportes - Notícias do esporte no Brasil e no mundo'
__author__ = 'Luciano Furtado'
language = 'pt'
category = 'esportes, Brasil'
no_stylesheets = True
oldest_article = 7
use_embedded_content=0
max_articles_per_feed = 10
cover_url = 'http://imgs.mg.superesportes.com.br/superesportes_logo.png'
extra_css = 'div.info_noticias h1 { font-size: 100% }'
remove_tags = [
dict(name='div',attrs={'class':'topo'}),
dict(name='div',attrs={'class':'rodape'}),
dict(name='div',attrs={'class':'navegacao'}),
dict(name='div',attrs={'class':'lateral2'}),
dict(name='div',attrs={'class':'leia_mais'}),
dict(name='div',attrs={'id':'comentar'}),
dict(name='div',attrs={'id':'vrumelc_noticia'}),
dict(name='div',attrs={'class':'compartilhe'}),
dict(name='div',attrs={'class':'linha_noticias'}),
dict(name='div',attrs={'class':'botoes_noticias'}),
dict(name='div',attrs={'class':'barra_time bg_time'}),
]
def parse_index(self):
feeds = []
sections = [
(u'Atletico', 'http://www.df.superesportes.com.br/futebol/atletico-mg/capa_atletico_mg/index.shtml'),
(u'Botafogo', 'http://www.df.superesportes.com.br/futebol/botafogo/capa_botafogo/index.shtml'),
(u'Corinthinas', 'http://www.df.superesportes.com.br/futebol/corinthians/capa_corinthians/index.shtml'),
(u'Cruzeiro', 'http://www.df.superesportes.com.br/futebol/cruzeiro/capa_cruzeiro/index.shtml'),
(u'Flamengo', 'http://www.df.superesportes.com.br/futebol/flamengo/capa_flamengo/index.shtml'),
(u'Fluminense', 'http://www.df.superesportes.com.br/futebol/fluminense/capa_fluminense/index.shtml'),
(u'Palmeiras', 'http://www.df.superesportes.com.br/futebol/palmeiras/capa_palmeiras/index.shtml'),
(u'Santos', 'http://www.df.superesportes.com.br/futebol/santos/capa_santos/index.shtml'),
(u'S√£o Paulo', 'http://www.df.superesportes.com.br/futebol/sao-paulo/capa_sao_paulo/index.shtml'),
(u'Vasco', 'http://www.df.superesportes.com.br/futebol/vasco/capa_vasco/index.shtml'),
]
for section, url in sections:
current_articles = []
soup = self.index_to_soup(url)
latestNews = soup.find(name='ul',attrs={'class': 'lista_ultimas_noticias'})
for li_tag in latestNews.findAll(name='li'):
a_tag = li_tag.find('a', href= True)
if a_tag is None:
continue
title = self.tag_to_string(a_tag)
url = a_tag.get('href', False)
self.log("\n\nFound title: " + title + "\nUrl: " + url + "\nSection: " + section)
current_articles.append({'title': title, 'url': url, 'description': title, 'date':''})
if current_articles:
feeds.append((section, current_articles))
return feeds

View File

@ -0,0 +1,45 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1284927619(BasicNewsRecipe):
title = u'Tagesanzeiger'
publisher = u'Tamedia AG'
oldest_article = 2
__author__ = 'noxxx'
max_articles_per_feed = 100
description = 'tagesanzeiger.ch: Nichts verpassen'
category = 'News, Politik, Nachrichten, Schweiz, Zürich'
language = 'de'
conversion_options = {
'comments' : description
,'tags' : category
,'language' : language
,'publisher' : publisher
}
remove_tags = [
dict(name='img')
,dict(name='div',attrs={'class':['swissquote ad','boxNews','centerAD','contentTabs2','sbsLabel']})
,dict(name='div',attrs={'id':['colRightAd','singleRight','singleSmallRight','MailInfo','metaLine','sidebarSky','contentFooter','commentInfo','commentInfo2','commentInfo3','footerBottom','clear','boxExclusiv','singleLogo','navSearch','headerLogin','headerBottomRight','horizontalNavigation','subnavigation','googleAdSense','footerAd','contentbox','articleGalleryNav']})
,dict(name='form',attrs={'id':['articleMailForm','commentform']})
,dict(name='div',attrs={'style':['position:absolute']})
,dict(name='script',attrs={'type':['text/javascript']})
,dict(name='p',attrs={'class':['schreiben','smallPrint','charCounter','caption']})
]
feeds = [
(u'Front', u'http://www.tagesanzeiger.ch/rss.html')
,(u'Zürich', u'http://www.tagesanzeiger.ch/zuerich/rss.html')
,(u'Schweiz', u'http://www.tagesanzeiger.ch/schweiz/rss.html')
,(u'Ausland', u'http://www.tagesanzeiger.ch/ausland/rss.html')
,(u'Digital', u'http://www.tagesanzeiger.ch/digital/rss.html')
,(u'Wissen', u'http://www.tagesanzeiger.ch/wissen/rss.html')
,(u'Panorama', u'http://www.tagesanzeiger.ch/panorama/rss.html')
,(u'Wirtschaft', u'http://www.tagesanzeiger.ch/wirtschaft/rss.html')
,(u'Sport', u'http://www.tagesanzeiger.ch/sport/rss.html')
,(u'Kultur', u'http://www.tagesanzeiger.ch/kultur/rss.html')
,(u'Leben', u'http://www.tagesanzeiger.ch/leben/rss.html')
,(u'Auto', u'http://www.tagesanzeiger.ch/auto/rss.html')]
def print_version(self, url):
return url + '/print.html'

View File

@ -0,0 +1,52 @@
import re
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1283848012(BasicNewsRecipe):
description = 'TheMarker Financial News in Hebrew'
__author__ = 'TonyTheBookworm, Marbs'
cover_url = 'http://static.ispot.co.il/wp-content/upload/2009/09/themarker.jpg'
title = u'TheMarker'
language = 'he'
simultaneous_downloads = 5
remove_javascript = True
timefmt = '[%a, %d %b, %Y]'
oldest_article = 1
remove_tags = [dict(name='tr', attrs={'bgcolor':['#738A94']}) ]
max_articles_per_feed = 10
extra_css='body{direction: rtl;} .article_description{direction: rtl; } a.article{direction: rtl; } .calibre_feed_description{direction: rtl; }'
feeds = [(u'Head Lines', u'http://www.themarker.com/tmc/content/xml/rss/hpfeed.xml'),
(u'TA Market', u'http://www.themarker.com/tmc/content/xml/rss/sections/marketfeed.xml'),
(u'Real Estate', u'http://www.themarker.com/tmc/content/xml/rss/sections/realEstaterfeed.xml'),
(u'Wall Street & Global', u'http://www.themarker.com/tmc/content/xml/rss/sections/wallsfeed.xml'),
(u'Law', u'http://www.themarker.com/tmc/content/xml/rss/sections/lawfeed.xml'),
(u'Media', u'http://www.themarker.com/tmc/content/xml/rss/sections/mediafeed.xml'),
(u'Consumer', u'http://www.themarker.com/tmc/content/xml/rss/sections/consumerfeed.xml'),
(u'Career', u'http://www.themarker.com/tmc/content/xml/rss/sections/careerfeed.xml'),
(u'Car', u'http://www.themarker.com/tmc/content/xml/rss/sections/carfeed.xml'),
(u'High Tech', u'http://www.themarker.com/tmc/content/xml/rss/sections/hightechfeed.xml'),
(u'Investor Guide', u'http://www.themarker.com/tmc/content/xml/rss/sections/investorGuidefeed.xml')]
def print_version(self, url):
split1 = url.split("=")
weblinks = url
if weblinks is not None:
for link in weblinks:
#---------------------------------------------------------
#here we need some help with some regexpressions
#we are trying to find it.themarker.com in a url
#-----------------------------------------------------------
re1='.*?' # Non-greedy match on filler
re2='(it\\.themarker\\.com)' # Fully Qualified Domain Name 1
rg = re.compile(re1+re2,re.IGNORECASE|re.DOTALL)
m = rg.search(url)
if m:
split2 = url.split("article/")
print_url = 'http://it.themarker.com/tmit/PrintArticle/' + split2[1]
else:
print_url = 'http://www.themarker.com/ibo/misc/printFriendly.jhtml?ElementId=%2Fibo%2Frepositories%2Fstories%2Fm1_2000%2F' + split1[1]+'.xml'
return print_url

View File

@ -70,10 +70,13 @@ class WallStreetJournal(BasicNewsRecipe):
def wsj_add_feed(self,feeds,title,url): def wsj_add_feed(self,feeds,title,url):
self.log('Found section:', title) self.log('Found section:', title)
try:
if url.endswith('whatsnews'): if url.endswith('whatsnews'):
articles = self.wsj_find_wn_articles(url) articles = self.wsj_find_wn_articles(url)
else: else:
articles = self.wsj_find_articles(url) articles = self.wsj_find_articles(url)
except:
articles = []
if articles: if articles:
feeds.append((title, articles)) feeds.append((title, articles))
return feeds return feeds

View File

@ -54,10 +54,13 @@ class WallStreetJournal(BasicNewsRecipe):
def wsj_add_feed(self,feeds,title,url): def wsj_add_feed(self,feeds,title,url):
self.log('Found section:', title) self.log('Found section:', title)
try:
if url.endswith('whatsnews'): if url.endswith('whatsnews'):
articles = self.wsj_find_wn_articles(url) articles = self.wsj_find_wn_articles(url)
else: else:
articles = self.wsj_find_articles(url) articles = self.wsj_find_articles(url)
except:
articles = []
if articles: if articles:
feeds.append((title, articles)) feeds.append((title, articles))
return feeds return feeds

View File

@ -666,13 +666,17 @@ class ActionCopyToLibrary(InterfaceActionBase):
name = 'Copy To Library' name = 'Copy To Library'
actual_plugin = 'calibre.gui2.actions.copy_to_library:CopyToLibraryAction' actual_plugin = 'calibre.gui2.actions.copy_to_library:CopyToLibraryAction'
class ActionTweakEpub(InterfaceActionBase):
name = 'Tweak ePub'
actual_plugin = 'calibre.gui2.actions.tweak_epub:TweakEpubAction'
plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog, plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog,
ActionConvert, ActionDelete, ActionEditMetadata, ActionView, ActionConvert, ActionDelete, ActionEditMetadata, ActionView,
ActionFetchNews, ActionSaveToDisk, ActionShowBookDetails, ActionFetchNews, ActionSaveToDisk, ActionShowBookDetails,
ActionRestart, ActionOpenFolder, ActionConnectShare, ActionRestart, ActionOpenFolder, ActionConnectShare,
ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks, ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks,
ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary, ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary,
ActionCopyToLibrary] ActionCopyToLibrary, ActionTweakEpub]
# }}} # }}}

View File

@ -29,7 +29,9 @@ class ANDROID(USBMS):
# Sony Ericsson # Sony Ericsson
0xfce : { 0xd12e : [0x0100]}, 0xfce : { 0xd12e : [0x0100]},
0x18d1 : { 0x4e11 : [0x0100, 0x226], 0x4e12: [0x0100, 0x226]}, # Google
0x18d1 : { 0x4e11 : [0x0100, 0x226, 0x227], 0x4e12: [0x0100, 0x226,
0x227]},
# Samsung # Samsung
0x04e8 : { 0x681d : [0x0222, 0x0400], 0x04e8 : { 0x681d : [0x0222, 0x0400],

View File

@ -739,7 +739,7 @@ class ITUNES(DriverBase):
# Purge the booklist, self.cached_books, thumb cache # Purge the booklist, self.cached_books, thumb cache
for i,bl_book in enumerate(booklists[0]): for i,bl_book in enumerate(booklists[0]):
if DEBUG: if False:
self.log.info(" evaluating '%s' by '%s' uuid:%s" % self.log.info(" evaluating '%s' by '%s' uuid:%s" %
(bl_book.title, bl_book.author,bl_book.uuid)) (bl_book.title, bl_book.author,bl_book.uuid))

View File

@ -11,6 +11,10 @@ import re
from calibre.devices.usbms.driver import USBMS from calibre.devices.usbms.driver import USBMS
def is_alex(device_info):
return device_info[3] == u'Linux 2.6.28 with pxa3xx_u2d' and \
device_info[4] == u'Seleucia Disk'
class N516(USBMS): class N516(USBMS):
name = 'N516 driver' name = 'N516 driver'
@ -34,6 +38,9 @@ class N516(USBMS):
EBOOK_DIR_MAIN = 'e_book' EBOOK_DIR_MAIN = 'e_book'
SUPPORTS_SUB_DIRS = True SUPPORTS_SUB_DIRS = True
def can_handle(self, device_info, debug=False):
return not is_alex(device_info)
class THEBOOK(N516): class THEBOOK(N516):
name = 'The Book driver' name = 'The Book driver'
gui_name = 'The Book' gui_name = 'The Book'
@ -61,6 +68,9 @@ class ALEX(N516):
EBOOK_DIR_MAIN = 'eBooks' EBOOK_DIR_MAIN = 'eBooks'
SUPPORTS_SUB_DIRS = True SUPPORTS_SUB_DIRS = True
def can_handle(self, device_info, debug=False):
return is_alex(device_info)
class AZBOOKA(ALEX): class AZBOOKA(ALEX):
name = 'Azbooka driver' name = 'Azbooka driver'
@ -74,6 +84,9 @@ class AZBOOKA(ALEX):
EBOOK_DIR_MAIN = '' EBOOK_DIR_MAIN = ''
def can_handle(self, device_info, debug=False):
return not is_alex(device_info)
class EB511(USBMS): class EB511(USBMS):
name = 'Elonex EB 511 driver' name = 'Elonex EB 511 driver'

View File

@ -98,6 +98,8 @@ class KOBO(USBMS):
if readstatus == 1: if readstatus == 1:
playlist_map[lpath]= "Im_Reading" playlist_map[lpath]= "Im_Reading"
elif readstatus == 2:
playlist_map[lpath]= "Read"
path = self.normalize_path(path) path = self.normalize_path(path)
# print "Normalized FileName: " + path # print "Normalized FileName: " + path
@ -441,11 +443,16 @@ class KOBO(USBMS):
connection = sqlite.connect(self._main_prefix + '.kobo/KoboReader.sqlite') connection = sqlite.connect(self._main_prefix + '.kobo/KoboReader.sqlite')
cursor = connection.cursor() cursor = connection.cursor()
if collections:
# Process any collections that exist
for category, books in collections.items():
if category == 'Im_Reading':
# Reset Im_Reading list in the database # Reset Im_Reading list in the database
if oncard == 'carda': if oncard == 'carda':
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID like \'file:///mnt/sd/%\'' query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 1 and ContentID like \'file:///mnt/sd/%\''
elif oncard != 'carda' and oncard != 'cardb': elif oncard != 'carda' and oncard != 'cardb':
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID not like \'file:///mnt/sd/%\'' query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 1 and ContentID not like \'file:///mnt/sd/%\''
try: try:
cursor.execute (query) cursor.execute (query)
@ -456,8 +463,6 @@ class KOBO(USBMS):
# debug_print('Commit: Reset Im_Reading list') # debug_print('Commit: Reset Im_Reading list')
connection.commit() connection.commit()
for category, books in collections.items():
if category == 'Im_Reading':
for book in books: for book in books:
# debug_print('Title:', book.title, 'lpath:', book.path) # debug_print('Title:', book.title, 'lpath:', book.path)
book.device_collections = ['Im_Reading'] book.device_collections = ['Im_Reading']
@ -478,6 +483,59 @@ class KOBO(USBMS):
else: else:
connection.commit() connection.commit()
# debug_print('Database: Commit create Im_Reading list') # debug_print('Database: Commit create Im_Reading list')
if category == 'Read':
# Reset Im_Reading list in the database
if oncard == 'carda':
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 2 and ContentID like \'file:///mnt/sd/%\''
elif oncard != 'carda' and oncard != 'cardb':
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ReadStatus = 2 and ContentID not like \'file:///mnt/sd/%\''
try:
cursor.execute (query)
except:
debug_print('Database Exception: Unable to reset Im_Reading list')
raise
else:
# debug_print('Commit: Reset Im_Reading list')
connection.commit()
for book in books:
# debug_print('Title:', book.title, 'lpath:', book.path)
book.device_collections = ['Read']
extension = os.path.splitext(book.path)[1]
ContentType = self.get_content_type_from_extension(extension)
ContentID = self.contentid_from_path(book.path, ContentType)
# datelastread = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())
t = (ContentID,)
try:
cursor.execute('update content set ReadStatus=2,FirstTimeReading=\'true\' where BookID is Null and ContentID = ?', t)
except:
debug_print('Database Exception: Unable set book as Rinished')
raise
else:
connection.commit()
# debug_print('Database: Commit set ReadStatus as Finished')
else: # No collections
# Since no collections exist the ReadStatus needs to be reset to 0 (Unread)
print "Reseting ReadStatus to 0"
# Reset Im_Reading list in the database
if oncard == 'carda':
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID like \'file:///mnt/sd/%\''
elif oncard != 'carda' and oncard != 'cardb':
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID not like \'file:///mnt/sd/%\''
try:
cursor.execute (query)
except:
debug_print('Database Exception: Unable to reset Im_Reading list')
raise
else:
# debug_print('Commit: Reset Im_Reading list')
connection.commit()
cursor.close() cursor.close()
connection.close() connection.close()

View File

@ -241,7 +241,7 @@ OptionRecommendation(name='toc_filter',
OptionRecommendation(name='chapter', OptionRecommendation(name='chapter',
recommended_value="//*[((name()='h1' or name()='h2') and " recommended_value="//*[((name()='h1' or name()='h2') and "
r"re:test(., 'chapter|book|section|part\s+', 'i')) or @class " r"re:test(., 'chapter|book|section|part|prologue|epilogue\s+', 'i')) or @class "
"= 'chapter']", level=OptionRecommendation.LOW, "= 'chapter']", level=OptionRecommendation.LOW,
help=_('An XPath expression to detect chapter titles. The default ' help=_('An XPath expression to detect chapter titles. The default '
'is to consider <h1> or <h2> tags that contain the words ' 'is to consider <h1> or <h2> tags that contain the words '

View File

@ -1574,6 +1574,7 @@ class MobiWriter(object):
id = unicode(oeb.metadata.cover[0]) id = unicode(oeb.metadata.cover[0])
item = oeb.manifest.ids[id] item = oeb.manifest.ids[id]
href = item.href href = item.href
if href in self._images:
index = self._images[href] - 1 index = self._images[href] - 1
exth.write(pack('>III', 0xc9, 0x0c, index)) exth.write(pack('>III', 0xc9, 0x0c, index))
exth.write(pack('>III', 0xcb, 0x0c, 0)) exth.write(pack('>III', 0xcb, 0x0c, 0))

View File

@ -1,7 +1,7 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
""" The GUI """ """ The GUI """
import os, sys import os, sys, Queue
from threading import RLock from threading import RLock
from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \ from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \
@ -39,7 +39,7 @@ gprefs.defaults['action-layout-context-menu'] = (
'Edit Metadata', 'Send To Device', 'Save To Disk', 'Edit Metadata', 'Send To Device', 'Save To Disk',
'Connect Share', 'Copy To Library', None, 'Connect Share', 'Copy To Library', None,
'Convert Books', 'View', 'Open Folder', 'Show Book Details', 'Convert Books', 'View', 'Open Folder', 'Show Book Details',
'Similar Books', None, 'Remove Books', 'Similar Books', 'Tweak ePub', None, 'Remove Books',
) )
gprefs.defaults['action-layout-context-menu-device'] = ( gprefs.defaults['action-layout-context-menu-device'] = (
@ -296,6 +296,34 @@ class Dispatcher(QObject):
def dispatch(self, args, kwargs): def dispatch(self, args, kwargs):
self.func(*args, **kwargs) self.func(*args, **kwargs)
class FunctionDispatcher(QObject):
'''
Convenience class to use Qt signals with arbitrary python functions.
By default, ensures that a function call always happens in the
thread this Dispatcher was created in.
'''
dispatch_signal = pyqtSignal(object, object, object)
def __init__(self, func, queued=True, parent=None):
QObject.__init__(self, parent)
self.func = func
typ = Qt.QueuedConnection
if not queued:
typ = Qt.AutoConnection if queued is None else Qt.DirectConnection
self.dispatch_signal.connect(self.dispatch, type=typ)
def __call__(self, *args, **kwargs):
q = Queue.Queue()
self.dispatch_signal.emit(q, args, kwargs)
return q.get()
def dispatch(self, q, args, kwargs):
try:
res = self.func(*args, **kwargs)
except:
res = None
q.put(res)
class GetMetadata(QObject): class GetMetadata(QObject):
''' '''
Convenience class to ensure that metadata readers are used only in the Convenience class to ensure that metadata readers are used only in the
@ -575,18 +603,6 @@ class Application(QApplication):
self._file_open_paths = [] self._file_open_paths = []
self._file_open_lock = RLock() self._file_open_lock = RLock()
if islinux:
self.setStyleSheet('''
QToolTip {
border: 2px solid black;
padding: 5px;
border-radius: 10px;
opacity: 200;
background-color: #e1e1ff;
color: black;
}
''')
def _send_file_open_events(self): def _send_file_open_events(self):
with self._file_open_lock: with self._file_open_lock:
if self._file_open_paths: if self._file_open_paths:

View File

@ -0,0 +1,55 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from calibre.gui2 import error_dialog
from calibre.gui2.actions import InterfaceAction
from calibre.gui2.dialogs.tweak_epub import TweakEpub
class TweakEpubAction(InterfaceAction):
name = 'Tweak ePub'
action_spec = (_('Tweak ePub'), 'trim.png',
_('Make small changes to ePub format books'),
_('T'))
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
action_type = 'current'
def genesis(self):
self.qaction.triggered.connect(self.edit_epub_in_situ)
def edit_epub_in_situ(self, *args):
row = self.gui.library_view.currentIndex()
if not row.isValid():
return error_dialog(self.gui, _('Cannot tweak ePub'),
_('No book selected'), show=True)
# Confirm 'EPUB' in formats
book_id = self.gui.library_view.model().id(row)
try:
path_to_epub = self.gui.library_view.model().db.format_abspath(
book_id, 'EPUB', index_is_id=True)
except:
path_to_epub = None
if not path_to_epub:
return error_dialog(self.gui, _('Cannot tweak ePub'),
_('No ePub available. First convert the book to ePub.'),
show=True)
# Launch modal dialog waiting for user to tweak or cancel
dlg = TweakEpub(self.gui, path_to_epub)
if dlg.exec_() == dlg.Accepted:
self.update_db(book_id, dlg._output)
dlg.cleanup()
def update_db(self, book_id, rebuilt):
'''
Update the calibre db with the tweaked epub
'''
self.gui.library_view.model().db.add_format(book_id, 'EPUB',
open(rebuilt, 'rb'), index_is_id=True)

View File

@ -800,7 +800,7 @@ class DeviceMixin(object): # {{{
# if set_books_in_library did not. # if set_books_in_library did not.
if not self.set_books_in_library(self.booklists(), reset=True): if not self.set_books_in_library(self.booklists(), reset=True):
self.upload_booklists() self.upload_booklists()
self.book_on_device(None, None, reset=True) self.book_on_device(None, reset=True)
# We need to reset the ondevice flags in the library. Use a big hammer, # We need to reset the ondevice flags in the library. Use a big hammer,
# so we don't need to worry about whether some succeeded or not. # so we don't need to worry about whether some succeeded or not.
self.refresh_ondevice_info(device_connected=True, reset_only=False) self.refresh_ondevice_info(device_connected=True, reset_only=False)
@ -1309,7 +1309,7 @@ class DeviceMixin(object): # {{{
for f in files: for f in files:
getattr(f, 'close', lambda : True)() getattr(f, 'close', lambda : True)()
def book_on_device(self, id, format=None, reset=False): def book_on_device(self, id, reset=False):
''' '''
Return an indication of whether the given book represented by its db id Return an indication of whether the given book represented by its db id
is on the currently connected device. It returns a 5 element list. The is on the currently connected device. It returns a 5 element list. The
@ -1338,8 +1338,6 @@ class DeviceMixin(object): # {{{
self.book_db_id_cache.append(set()) self.book_db_id_cache.append(set())
for book in l: for book in l:
db_id = getattr(book, 'application_id', None) db_id = getattr(book, 'application_id', None)
if db_id is None:
db_id = book.db_id
if db_id is not None: if db_id is not None:
# increment the count of books on the device with this # increment the count of books on the device with this
# db_id. # db_id.

View File

@ -300,6 +300,24 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.cpixmap = pix self.cpixmap = pix
self.cover_data = cdata self.cover_data = cdata
def trim_cover(self, *args):
from calibre.utils.magick import Image
cdata = self.cover_data
if not cdata:
return
im = Image()
im.load(cdata)
im.trim(10)
cdata = im.export('jpg')
pix = QPixmap()
pix.loadFromData(cdata)
self.cover.setPixmap(pix)
self.cover_changed = True
self.cpixmap = pix
self.cover_data = cdata
def sync_formats(self): def sync_formats(self):
old_extensions, new_extensions, paths = set(), set(), {} old_extensions, new_extensions, paths = set(), set(), {}
for row in range(self.formats.count()): for row in range(self.formats.count()):
@ -380,6 +398,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.remove_unused_series) self.remove_unused_series)
QObject.connect(self.auto_author_sort, SIGNAL('clicked()'), QObject.connect(self.auto_author_sort, SIGNAL('clicked()'),
self.deduce_author_sort) self.deduce_author_sort)
self.trim_cover_button.clicked.connect(self.trim_cover)
self.connect(self.author_sort, SIGNAL('textChanged(const QString&)'), self.connect(self.author_sort, SIGNAL('textChanged(const QString&)'),
self.author_sort_box_changed) self.author_sort_box_changed)
self.connect(self.authors, SIGNAL('editTextChanged(const QString&)'), self.connect(self.authors, SIGNAL('editTextChanged(const QString&)'),

View File

@ -625,6 +625,17 @@ Using this button to create author sort will change author sort from red to gree
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QToolButton" name="trim_cover_button">
<property name="toolTip">
<string>Remove border (if any) from cover</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/trim.png</normaloff>:/images/trim.png</iconset>
</property>
</widget>
</item>
<item> <item>
<widget class="QToolButton" name="reset_cover"> <widget class="QToolButton" name="reset_cover">
<property name="toolTip"> <property name="toolTip">

View File

@ -0,0 +1,81 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, shutil
from contextlib import closing
from zipfile import ZipFile, ZIP_DEFLATED, ZIP_STORED
from PyQt4.Qt import QDialog
from calibre.gui2 import open_local_file
from calibre.gui2.dialogs.tweak_epub_ui import Ui_Dialog
from calibre.libunzip import extract as zipextract
from calibre.ptempfile import PersistentTemporaryDirectory
class TweakEpub(QDialog, Ui_Dialog):
'''
Display controls for tweaking ePubs
'''
def __init__(self, parent, epub):
QDialog.__init__(self, parent)
self._epub = epub
self._exploded = None
self._output = None
# Run the dialog setup generated from tweak_epub.ui
self.setupUi(self)
self.cancel_button.clicked.connect(self.reject)
self.explode_button.clicked.connect(self.explode)
self.rebuild_button.clicked.connect(self.rebuild)
# Position update dialog overlaying top left of app window
parent_loc = parent.pos()
self.move(parent_loc.x(),parent_loc.y())
def cleanup(self):
# Delete directory containing exploded ePub
if self._exploded is not None:
shutil.rmtree(self._exploded, ignore_errors=True)
def display_exploded(self):
'''
Generic subprocess launch of native file browser
User can use right-click to 'Open with ...'
'''
open_local_file(self._exploded)
def explode(self, *args):
if self._exploded is None:
self._exploded = PersistentTemporaryDirectory("_exploded", prefix='')
zipextract(self._epub, self._exploded)
self.display_exploded()
self.rebuild_button.setEnabled(True)
self.explode_button.setEnabled(False)
def rebuild(self, *args):
self._output = os.path.join(self._exploded, 'rebuilt.epub')
with closing(ZipFile(self._output, 'w', compression=ZIP_DEFLATED)) as zf:
# Write mimetype
zf.write(os.path.join(self._exploded,'mimetype'), 'mimetype', compress_type=ZIP_STORED)
# Write everything else
exclude_files = ['.DS_Store','mimetype','iTunesMetadata.plist','rebuilt.epub']
for root, dirs, files in os.walk(self._exploded):
for fn in files:
if fn in exclude_files:
continue
absfn = os.path.join(root, fn)
zfn = os.path.relpath(absfn,
self._exploded).replace(os.sep, '/')
zf.write(absfn, zfn)
return QDialog.accept(self)

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>382</width>
<height>242</height>
</rect>
</property>
<property name="windowTitle">
<string>Tweak ePub</string>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<property name="modal">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QPushButton" name="explode_button">
<property name="statusTip">
<string>Display contents of exploded ePub</string>
</property>
<property name="text">
<string>&amp;Explode ePub</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/images/wizard.png</normaloff>:/images/wizard.png</iconset>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="rebuild_button">
<property name="enabled">
<bool>false</bool>
</property>
<property name="statusTip">
<string>Rebuild ePub from exploded contents</string>
</property>
<property name="text">
<string>&amp;Rebuild ePub</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/images/exec.png</normaloff>:/images/exec.png</iconset>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QPushButton" name="cancel_button">
<property name="statusTip">
<string>Discard changes</string>
</property>
<property name="text">
<string>&amp;Cancel</string>
</property>
<property name="icon">
<iconset>
<normaloff>:/images/window-close.png</normaloff>:/images/window-close.png</iconset>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Explode the ePub to display contents in a file browser window. To tweak individual files, right-click, then 'Open with...' your editor of choice. When tweaks are complete, close the file browser window. Rebuild the ePub, updating your calibre library.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../../resources/images.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -12,7 +12,7 @@ from operator import attrgetter
from PyQt4.Qt import QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, \ from PyQt4.Qt import QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, \
QModelIndex, QVariant, QDate QModelIndex, QVariant, QDate
from calibre.gui2 import NONE, config, UNDEFINED_QDATE from calibre.gui2 import NONE, config, UNDEFINED_QDATE, FunctionDispatcher
from calibre.utils.pyparsing import ParseException from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
@ -151,7 +151,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.database_changed.emit(db) self.database_changed.emit(db)
if self.cover_cache is not None: if self.cover_cache is not None:
self.cover_cache.stop() self.cover_cache.stop()
self.cover_cache = CoverCache(db) self.cover_cache = CoverCache(db, FunctionDispatcher(self.db.cover))
self.cover_cache.start() self.cover_cache.start()
def refresh_cover(event, ids): def refresh_cover(event, ids):
if event == 'cover' and self.cover_cache is not None: if event == 'cover' and self.cover_cache is not None:

View File

@ -217,6 +217,10 @@ def fetch_scheduled_recipe(arg):
if 'output_profile' in ps: if 'output_profile' in ps:
recs.append(('output_profile', ps['output_profile'], recs.append(('output_profile', ps['output_profile'],
OptionRecommendation.HIGH)) OptionRecommendation.HIGH))
if ps['output_profile'] == 'kindle':
recs.append(('no_inline_toc', True,
OptionRecommendation.HIGH))
lf = load_defaults('look_and_feel') lf = load_defaults('look_and_feel')
if lf.get('base_font_size', 0.0) != 0.0: if lf.get('base_font_size', 0.0) != 0.0:
recs.append(('base_font_size', lf['base_font_size'], recs.append(('base_font_size', lf['base_font_size'],

View File

@ -863,11 +863,11 @@ class SplitterHandle(QSplitterHandle):
self.update() self.update()
def paintEvent(self, ev): def paintEvent(self, ev):
QSplitterHandle.paintEvent(self, ev)
if self.highlight: if self.highlight:
painter = QPainter(self) painter = QPainter(self)
painter.setClipRect(ev.rect()) painter.setClipRect(ev.rect())
painter.fillRect(self.rect(), Qt.yellow) painter.fillRect(self.rect(), Qt.yellow)
QSplitterHandle.paintEvent(self, ev)
def mouseDoubleClickEvent(self, ev): def mouseDoubleClickEvent(self, ev):
self.double_clicked.emit(self) self.double_clicked.emit(self)

View File

@ -6,7 +6,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import re, itertools import re, itertools, time
from itertools import repeat from itertools import repeat
from datetime import timedelta from datetime import timedelta
from threading import Thread, RLock from threading import Thread, RLock
@ -23,10 +23,11 @@ from calibre import fit_image
class CoverCache(Thread): class CoverCache(Thread):
def __init__(self, db): def __init__(self, db, cover_func):
Thread.__init__(self) Thread.__init__(self)
self.daemon = True self.daemon = True
self.db = db self.db = db
self.cover_func = cover_func
self.load_queue = Queue() self.load_queue = Queue()
self.keep_running = True self.keep_running = True
self.cache = {} self.cache = {}
@ -37,7 +38,8 @@ class CoverCache(Thread):
self.keep_running = False self.keep_running = False
def _image_for_id(self, id_): def _image_for_id(self, id_):
img = self.db.cover(id_, index_is_id=True, as_image=True) time.sleep(0.050) # Limit 20/second to not overwhelm the GUI
img = self.cover_func(id_, index_is_id=True, as_image=True)
if img is None: if img is None:
img = QImage() img = QImage()
if not img.isNull(): if not img.isNull():

View File

@ -402,7 +402,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
path = path.lower() path = path.lower()
return path return path
def set_path(self, index, index_is_id=False, commit=True): def set_path(self, index, index_is_id=False):
''' '''
Set the path to the directory containing this books files based on its Set the path to the directory containing this books files based on its
current title and author. If there was a previous directory, its contents current title and author. If there was a previous directory, its contents
@ -432,7 +432,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if current_path and os.path.exists(spath): # Migrate existing files if current_path and os.path.exists(spath): # Migrate existing files
cdata = self.cover(id, index_is_id=True) cdata = self.cover(id, index_is_id=True)
if cdata is not None: if cdata is not None:
open(os.path.join(tpath, 'cover.jpg'), 'wb').write(cdata) with open(os.path.join(tpath, 'cover.jpg'), 'wb') as f:
f.write(cdata)
for format in formats: for format in formats:
# Get data as string (can't use file as source and target files may be the same) # Get data as string (can't use file as source and target files may be the same)
f = self.format(id, format, index_is_id=True, as_file=False) f = self.format(id, format, index_is_id=True, as_file=False)
@ -442,8 +443,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.add_format(id, format, stream, index_is_id=True, self.add_format(id, format, stream, index_is_id=True,
path=tpath, notify=False) path=tpath, notify=False)
self.conn.execute('UPDATE books SET path=? WHERE id=?', (path, id)) self.conn.execute('UPDATE books SET path=? WHERE id=?', (path, id))
if commit:
self.conn.commit()
self.data.set(id, self.FIELD_MAP['path'], path, row_is_id=True) self.data.set(id, self.FIELD_MAP['path'], path, row_is_id=True)
# Delete not needed directories # Delete not needed directories
if current_path and os.path.exists(spath): if current_path and os.path.exists(spath):
@ -1131,7 +1130,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def set_authors(self, id, authors, notify=True, commit=True): def set_authors(self, id, authors, notify=True, commit=True):
''' '''
`authors`: A list of authors. Note that even if commit is False, the db will still be committed to
because this causes the location of files to change
:param authors: A list of authors.
''' '''
if not authors: if not authors:
authors = [_('Unknown')] authors = [_('Unknown')]
@ -1163,11 +1165,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
','.join([a.replace(',', '|') for a in authors]), ','.join([a.replace(',', '|') for a in authors]),
row_is_id=True) row_is_id=True)
self.data.set(id, self.FIELD_MAP['author_sort'], ss, row_is_id=True) self.data.set(id, self.FIELD_MAP['author_sort'], ss, row_is_id=True)
self.set_path(id, index_is_id=True, commit=commit) self.set_path(id, index_is_id=True)
if notify: if notify:
self.notify('metadata', [id]) self.notify('metadata', [id])
def set_title(self, id, title, notify=True, commit=True): def set_title(self, id, title, notify=True, commit=True):
'''
Note that even if commit is False, the db will still be committed to
because this causes the location of files to change
'''
if not title: if not title:
return return
if not isinstance(title, unicode): if not isinstance(title, unicode):
@ -1178,7 +1184,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.data.set(id, self.FIELD_MAP['sort'], title_sort(title), row_is_id=True) self.data.set(id, self.FIELD_MAP['sort'], title_sort(title), row_is_id=True)
else: else:
self.data.set(id, self.FIELD_MAP['sort'], title, row_is_id=True) self.data.set(id, self.FIELD_MAP['sort'], title, row_is_id=True)
self.set_path(id, index_is_id=True, commit=commit) self.set_path(id, index_is_id=True)
if commit: if commit:
self.conn.commit() self.conn.commit()
if notify: if notify:

View File

@ -60,15 +60,15 @@ def identify(path):
data = open(path, 'rb').read() data = open(path, 'rb').read()
return identify_data(data) return identify_data(data)
def add_borders_to_image(path_to_image, left=0, top=0, right=0, bottom=0, def add_borders_to_image(img_data, left=0, top=0, right=0, bottom=0,
border_color='#ffffff'): border_color='#ffffff', fmt='jpg'):
img = Image() img = Image()
img.open(path_to_image) img.load(img_data)
lwidth, lheight = img.size lwidth, lheight = img.size
canvas = create_canvas(lwidth+left+right, lheight+top+bottom, canvas = create_canvas(lwidth+left+right, lheight+top+bottom,
border_color) border_color)
canvas.compose(img, left, top) canvas.compose(img, left, top)
canvas.save(path_to_image) return canvas.export(fmt)
def create_text_wand(font_size, font_path=None): def create_text_wand(font_size, font_path=None):
if font_path is None: if font_path is None:

View File

@ -7,7 +7,7 @@ Defines various abstract base classes that can be subclassed to create powerful
__docformat__ = "restructuredtext en" __docformat__ = "restructuredtext en"
import os, time, traceback, re, urlparse, sys import os, time, traceback, re, urlparse, sys, cStringIO
from collections import defaultdict from collections import defaultdict
from functools import partial from functools import partial
from contextlib import nested, closing from contextlib import nested, closing
@ -27,6 +27,7 @@ from calibre.web.fetch.simple import RecursiveFetcher
from calibre.utils.threadpool import WorkRequest, ThreadPool, NoResultsPending from calibre.utils.threadpool import WorkRequest, ThreadPool, NoResultsPending
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.date import now as nowf from calibre.utils.date import now as nowf
from calibre.utils.magick.draw import save_cover_data_to, add_borders_to_image
class LoginFailed(ValueError): class LoginFailed(ValueError):
pass pass
@ -948,38 +949,36 @@ class BasicNewsRecipe(Recipe):
try: try:
cu = self.get_cover_url() cu = self.get_cover_url()
except Exception, err: except Exception, err:
cu = None
self.log.error(_('Could not download cover: %s')%str(err)) self.log.error(_('Could not download cover: %s')%str(err))
self.log.debug(traceback.format_exc()) self.log.debug(traceback.format_exc())
if cu is not None: else:
ext = cu.split('/')[-1].rpartition('.')[-1] cdata = None
if '?' in ext:
ext = ''
ext = ext.lower() if ext and '/' not in ext else 'jpg'
cpath = os.path.join(self.output_dir, 'cover.'+ext)
if os.access(cu, os.R_OK): if os.access(cu, os.R_OK):
with open(cpath, 'wb') as cfile: cdata = open(cu, 'rb').read()
cfile.write(open(cu, 'rb').read())
else: else:
self.report_progress(1, _('Downloading cover from %s')%cu) self.report_progress(1, _('Downloading cover from %s')%cu)
with nested(open(cpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r): with closing(self.browser.open(cu)) as r:
cfile.write(r.read()) cdata = r.read()
if not cdata:
return
ext = cu.split('/')[-1].rpartition('.')[-1].lower().strip()
if ext == 'pdf':
from calibre.ebooks.metadata.pdf import get_metadata
stream = cStringIO.StringIO(cdata)
cdata = None
mi = get_metadata(stream)
if mi.cover_data and mi.cover_data[1]:
cdata = mi.cover_data[1]
if not cdata:
return
if self.cover_margins[0] or self.cover_margins[1]: if self.cover_margins[0] or self.cover_margins[1]:
from calibre.utils.magick.draw import add_borders_to_image cdata = add_borders_to_image(cdata,
add_borders_to_image(cpath,
left=self.cover_margins[0],right=self.cover_margins[0], left=self.cover_margins[0],right=self.cover_margins[0],
top=self.cover_margins[1],bottom=self.cover_margins[1], top=self.cover_margins[1],bottom=self.cover_margins[1],
border_color=self.cover_margins[2]) border_color=self.cover_margins[2])
if ext.lower() == 'pdf':
from calibre.ebooks.metadata.pdf import get_metadata cpath = os.path.join(self.output_dir, 'cover.jpg')
stream = open(cpath, 'rb') save_cover_data_to(cdata, cpath)
mi = get_metadata(stream)
cpath = None
if mi.cover_data and mi.cover_data[1]:
cpath = os.path.join(self.output_dir,
'cover.'+mi.cover_data[0])
with open(cpath, 'wb') as f:
f.write(mi.cover_data[1])
self.cover_path = cpath self.cover_path = cpath
def download_cover(self): def download_cover(self):
@ -1422,7 +1421,6 @@ class CalibrePeriodical(BasicNewsRecipe):
return br return br
def download(self): def download(self):
import cStringIO
self.log('Fetching downloaded recipe') self.log('Fetching downloaded recipe')
try: try:
raw = self.browser.open_novisit( raw = self.browser.open_novisit(