mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merged from trunk
This commit is contained in:
commit
0207aa03a4
589
imgsrc/rating.svg
Normal file
589
imgsrc/rating.svg
Normal file
@ -0,0 +1,589 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg
|
||||
xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://web.resource.org/cc/"
|
||||
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="svg2"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.45+devel"
|
||||
version="1.0"
|
||||
sodipodi:docbase="/Users/david/Progetti/oxygen-svn/theme/svg/actions"
|
||||
sodipodi:docname="rating.svgz"
|
||||
inkscape:output_extension="org.inkscape.output.svgz.inkscape"
|
||||
inkscape:export-filename="/home/pinheiro/pics/oxygen/scalable/actions/rating.png"
|
||||
inkscape:export-xdpi="11.25"
|
||||
inkscape:export-ydpi="11.25">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
id="linearGradient3946">
|
||||
<stop
|
||||
style="stop-color:#552b00;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3948" />
|
||||
<stop
|
||||
style="stop-color:#673400;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3950" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient3844">
|
||||
<stop
|
||||
style="stop-color:#faff64;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3846" />
|
||||
<stop
|
||||
style="stop-color:#faff64;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3848" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient3379">
|
||||
<stop
|
||||
style="stop-color:#fffc07;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3381" />
|
||||
<stop
|
||||
style="stop-color:#fffc07;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3383" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient3363">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3365" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3367" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3309">
|
||||
<stop
|
||||
style="stop-color:#f8ff8a;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop3311" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3313" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient26907"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-84.002403"
|
||||
y1="-383.9971"
|
||||
x2="-12.0029"
|
||||
y2="-383.9971"
|
||||
gradientTransform="matrix(0,1,-1,0,-39.9985,140.0029)">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#888a85;stop-opacity:1;"
|
||||
id="stop26909" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#2e3436;stop-opacity:1;"
|
||||
id="stop26911" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="matrix(0,1,-1,0,-39.9985,140.0029)"
|
||||
y2="-383.9975"
|
||||
x2="-23.516129"
|
||||
y1="-383.9971"
|
||||
x1="-84.002403"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient3711">
|
||||
<stop
|
||||
id="stop3713"
|
||||
style="stop-color:white;stop-opacity:1;"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop3715"
|
||||
style="stop-color:white;stop-opacity:0;"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3081">
|
||||
<stop
|
||||
id="stop3083"
|
||||
offset="0"
|
||||
style="stop-color:#28691f;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop3085"
|
||||
offset="1"
|
||||
style="stop-color:#00bf00;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3290">
|
||||
<stop
|
||||
style="stop-color:yellow;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3292" />
|
||||
<stop
|
||||
style="stop-color:#f07800;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3294" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3638">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="0"
|
||||
id="stop3640" />
|
||||
<stop
|
||||
id="stop3661"
|
||||
offset="0.06868132"
|
||||
style="stop-color:#ffffff;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop3659"
|
||||
offset="0.5"
|
||||
style="stop-color:#ffffff;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop3642" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient1563">
|
||||
<stop
|
||||
id="stop1565"
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop1567"
|
||||
offset="1"
|
||||
style="stop-color:white;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3273">
|
||||
<stop
|
||||
id="stop3275"
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:0.55035973;" />
|
||||
<stop
|
||||
id="stop3277"
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient12948">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop12950" />
|
||||
<stop
|
||||
style="stop-color:#c0c0c0;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop12952" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,0,0,0.111111,0,138.1081)"
|
||||
r="64.796692"
|
||||
fy="177.29686"
|
||||
fx="80.738739"
|
||||
cy="155.37218"
|
||||
cx="80.738739"
|
||||
id="radialGradient5079"
|
||||
xlink:href="#linearGradient5073"
|
||||
inkscape:collect="always" />
|
||||
<linearGradient
|
||||
id="linearGradient5073"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop5075"
|
||||
offset="0"
|
||||
style="stop-color:#000000;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop5077"
|
||||
offset="1"
|
||||
style="stop-color:#000000;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<foreignObject
|
||||
id="foreignObject7221"
|
||||
height="1"
|
||||
width="1"
|
||||
y="0"
|
||||
x="0"
|
||||
requiredExtensions="http://ns.adobe.com/AdobeIllustrator/10.0/">
|
||||
<i:pgfRef
|
||||
xlink:href="#adobe_illustrator_pgf" />
|
||||
</foreignObject>
|
||||
<linearGradient
|
||||
id="XMLID_1_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="95.693398"
|
||||
y1="141.1738"
|
||||
x2="32.308601"
|
||||
y2="77.789001">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#ffd50a;stop-opacity:1;"
|
||||
id="stop7227" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#8d4000;stop-opacity:1;"
|
||||
id="stop7233" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="XMLID_3_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="63.9995"
|
||||
y1="92.865196"
|
||||
x2="63.9995"
|
||||
y2="120.8652"
|
||||
gradientTransform="translate(175.0067,11.74752)">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#888A85"
|
||||
id="stop7261" />
|
||||
<stop
|
||||
offset="0.3226"
|
||||
style="stop-color:#A6A7A3"
|
||||
id="stop7263" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#EEEEEC"
|
||||
id="stop7265" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="XMLID_4_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="64.000504"
|
||||
y1="108.8652"
|
||||
x2="64.000504"
|
||||
y2="92.865196">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#EEEEEC"
|
||||
id="stop7270" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#FFFFFF"
|
||||
id="stop7272" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3081"
|
||||
id="linearGradient2149"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="62.112335"
|
||||
y1="90.513916"
|
||||
x2="67.887672"
|
||||
y2="39.095695" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient26907"
|
||||
id="linearGradient3226"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0,1,-1,0,-39.9985,140.0029)"
|
||||
x1="-70.002899"
|
||||
y1="-383.9971"
|
||||
x2="-11.91648"
|
||||
y2="-383.9971" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3711"
|
||||
id="radialGradient3228"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
cx="343.99899"
|
||||
cy="92"
|
||||
fx="343.99899"
|
||||
fy="92"
|
||||
r="36" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3711"
|
||||
id="linearGradient3230"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0,1.022977,-1.022977,0,111.9686,137.8125)"
|
||||
x1="-88.058083"
|
||||
y1="-131.93112"
|
||||
x2="-45.096584"
|
||||
y2="-131.93112" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#XMLID_1_"
|
||||
id="linearGradient2898"
|
||||
x1="64.07962"
|
||||
y1="-14.227339"
|
||||
x2="64.07962"
|
||||
y2="120.44466"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3290"
|
||||
id="radialGradient2906"
|
||||
cx="69.526619"
|
||||
cy="60.115833"
|
||||
fx="69.526619"
|
||||
fy="89.655701"
|
||||
r="111.65377"
|
||||
gradientTransform="matrix(0.9439139,-0.3301918,0.332644,0.9509241,-16.097695,27.249949)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3290"
|
||||
id="radialGradient3304"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.5227399,0,-1.554444e-8,0.5266221,349.81061,60.575712)"
|
||||
cx="69.526619"
|
||||
cy="60.115833"
|
||||
fx="69.526619"
|
||||
fy="60.115833"
|
||||
r="111.65377" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3309"
|
||||
id="linearGradient3315"
|
||||
x1="219.22163"
|
||||
y1="11.902248"
|
||||
x2="219.22163"
|
||||
y2="136.85997"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-170.08594,0)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1563"
|
||||
id="linearGradient3345"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-37.771032,-0.1213203)"
|
||||
x1="278.47162"
|
||||
y1="77.652245"
|
||||
x2="200.17728"
|
||||
y2="31.10997" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3363"
|
||||
id="linearGradient3369"
|
||||
x1="177.42397"
|
||||
y1="22.377773"
|
||||
x2="177.60074"
|
||||
y2="93.022789"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3379"
|
||||
id="linearGradient3385"
|
||||
x1="216.88614"
|
||||
y1="122.5867"
|
||||
x2="216.88614"
|
||||
y2="37.969955"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-152,0)" />
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
id="filter3391">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="0.55939545"
|
||||
id="feGaussianBlur3393" />
|
||||
</filter>
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
id="filter3401">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="0.11157909"
|
||||
id="feGaussianBlur3403" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3363"
|
||||
id="linearGradient3800"
|
||||
x1="63.948792"
|
||||
y1="12.034382"
|
||||
x2="67.219337"
|
||||
y2="12.034382"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
spreadMethod="reflect" />
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
id="filter3838"
|
||||
x="-0.17816916"
|
||||
width="1.3563383"
|
||||
y="-0.15506857"
|
||||
height="1.3101371">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="0.46259975"
|
||||
id="feGaussianBlur3840" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3844"
|
||||
id="linearGradient3850"
|
||||
x1="28.637825"
|
||||
y1="120.84999"
|
||||
x2="31.289474"
|
||||
y2="122.08743"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
spreadMethod="reflect" />
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
id="filter3928">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="0.18346262"
|
||||
id="feGaussianBlur3930" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3844"
|
||||
id="linearGradient3934"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
spreadMethod="reflect"
|
||||
x1="28.637825"
|
||||
y1="120.84999"
|
||||
x2="31.289474"
|
||||
y2="122.08743" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3946"
|
||||
id="radialGradient3956"
|
||||
cx="64.07962"
|
||||
cy="114.47154"
|
||||
fx="64.07962"
|
||||
fy="114.47154"
|
||||
r="60.700505"
|
||||
gradientTransform="matrix(0.2787307,0,0,0.2689969,46.218665,81.520439)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
id="filter3975">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="1.2948866"
|
||||
id="feGaussianBlur3977" />
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.4142136"
|
||||
inkscape:cx="-57.231582"
|
||||
inkscape:cy="95.226607"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:window-width="1247"
|
||||
inkscape:window-height="816"
|
||||
inkscape:window-x="388"
|
||||
inkscape:window-y="110"
|
||||
showgrid="true"
|
||||
gridspacingx="4px"
|
||||
gridspacingy="4px"
|
||||
gridempspacing="0"
|
||||
inkscape:grid-points="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2302"
|
||||
spacingx="4px"
|
||||
spacingy="4px"
|
||||
empspacing="2" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
id="path3961"
|
||||
d="M 64.03125 8 C 56.162818 8.0100117 46.828561 34.554451 40.46875 39.1875 C 34.10894 43.820548 5.9844574 44.576082 3.5625 52.0625 C 1.1405426 59.548917 23.465249 76.613524 25.90625 84.09375 C 28.347251 91.57398 20.40967 118.5394 26.78125 123.15625 C 33.15283 127.7731 56.287818 111.82251 64.15625 111.8125 C 72.024682 111.80249 95.202691 127.69555 101.5625 123.0625 C 107.92231 118.42945 99.890544 91.486414 102.3125 84 C 104.73446 76.513583 127.03475 59.38648 124.59375 51.90625 C 122.15275 44.426021 94.027829 43.741849 87.65625 39.125 C 81.28467 34.508152 71.899685 7.9899879 64.03125 8 z M 64.03125 11.90625 C 64.208046 12.045423 65.56776 12.712264 67.15625 14.65625 C 68.97167 16.877947 71.031426 20.210059 73.0625 23.75 C 75.093573 27.28994 77.113982 31.048819 79.09375 34.3125 C 81.073519 37.576182 82.75512 40.328991 85.40625 42.25 C 88.057376 44.171009 91.18831 44.91637 94.90625 45.78125 C 98.624192 46.646129 102.81606 47.391152 106.8125 48.21875 C 110.80894 49.046347 114.60465 49.966787 117.28125 51 C 119.62327 51.904061 120.71845 53.000764 120.90625 53.125 C 120.82618 53.333062 120.57672 54.794782 119.21875 56.90625 C 117.66679 59.319356 115.1453 62.318181 112.40625 65.34375 C 109.66721 68.369316 106.71091 71.452346 104.21875 74.34375 C 101.72659 77.235155 99.632744 79.697501 98.625 82.8125 C 97.617256 85.927495 97.892393 89.134266 98.21875 92.9375 C 98.545107 96.740738 99.114622 100.97466 99.5625 105.03125 C 100.01038 109.08783 100.31178 112.97888 100.15625 115.84375 C 100.02016 118.35052 99.34151 119.69095 99.28125 119.90625 C 99.057443 119.89786 97.552762 120.17027 95.125 119.53125 C 92.350417 118.80093 88.723899 117.29504 85 115.625 C 81.276103 113.95497 77.426259 112.10169 73.90625 110.625 C 70.386242 109.1483 67.4302 107.93334 64.15625 107.9375 C 60.882303 107.94167 57.891241 109.1706 54.375 110.65625 C 50.858761 112.1419 47.032137 114.00799 43.3125 115.6875 C 39.592862 117.367 35.960216 118.85638 33.1875 119.59375 C 30.761373 120.23895 29.286908 119.99088 29.0625 120 C 29.004012 119.7864 28.29872 118.4439 28.15625 115.9375 C 27.993428 113.07303 28.281199 109.18271 28.71875 105.125 C 29.156299 101.0673 29.714573 96.835302 30.03125 93.03125 C 30.347928 89.227198 30.609418 85.987425 29.59375 82.875 C 28.578082 79.762573 26.468263 77.322553 23.96875 74.4375 C 21.469238 71.552452 18.527988 68.487339 15.78125 65.46875 C 13.034512 62.450158 10.495601 59.471649 8.9375 57.0625 C 7.5741618 54.954496 7.3592053 53.457399 7.28125 53.25 C 7.2962039 53.337785 8.2681026 52.126785 10.84375 51.125 C 13.517705 50.084977 17.34943 49.150265 21.34375 48.3125 C 25.33807 47.474737 29.534272 46.749339 33.25 45.875 C 36.96573 45.000663 40.103767 44.24025 42.75 42.3125 C 45.396234 40.384748 47.059794 37.612458 49.03125 34.34375 C 51.002705 31.075042 53.009191 27.326347 55.03125 23.78125 C 57.053308 20.236153 59.096493 16.88256 60.90625 14.65625 C 62.489787 12.708229 63.857465 12.044552 64.03125 11.90625 z "
|
||||
style="opacity:1;fill:url(#linearGradient2898);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:14.80851269000000059;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1;filter:url(#filter3975)" />
|
||||
<path
|
||||
transform="matrix(0.4934214,0.1726044,-0.1726044,0.4934214,42.377875,49.908537)"
|
||||
d="M 153.09403,94.713757 C 144.53658,107.09689 92.616372,93.013297 78.414631,98.001518 C 64.21289,102.98974 32.50348,146.4474 18.082028,142.13539 C 3.6605746,137.82337 1.0106378,84.092245 -8.1220219,72.127031 C -17.254681,60.161818 -68.384124,43.433534 -68.739625,28.385431 C -69.095125,13.337327 -18.812666,-5.7867426 -10.255219,-18.169872 C -1.697772,-30.553002 -1.5880954,-84.349316 12.613645,-89.337536 C 26.815387,-94.325757 60.541592,-52.41396 74.963045,-48.101941 C 89.384498,-43.789923 140.58172,-60.30959 149.71438,-48.344376 C 158.84704,-36.379162 129.40853,8.6478227 129.76403,23.695927 C 130.11953,38.74403 161.65148,82.330628 153.09403,94.713757 z"
|
||||
inkscape:randomized="0"
|
||||
inkscape:rounded="0.20136392"
|
||||
inkscape:flatsided="false"
|
||||
sodipodi:arg2="1.2330172"
|
||||
sodipodi:arg1="0.60469864"
|
||||
sodipodi:r2="76.832565"
|
||||
sodipodi:r1="121.72647"
|
||||
sodipodi:cy="25.510532"
|
||||
sodipodi:cx="52.952892"
|
||||
sodipodi:sides="5"
|
||||
id="path3574"
|
||||
style="opacity:1;fill:url(#radialGradient2906);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:14.80892944000000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1"
|
||||
sodipodi:type="star" />
|
||||
<path
|
||||
style="opacity:1;fill:url(#linearGradient2898);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:14.80851269000000059;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1"
|
||||
d="M 64.03125 8 C 56.162818 8.0100117 46.828561 34.554451 40.46875 39.1875 C 34.10894 43.820548 5.9844574 44.576082 3.5625 52.0625 C 1.1405426 59.548917 23.465249 76.613524 25.90625 84.09375 C 28.347251 91.57398 20.40967 118.5394 26.78125 123.15625 C 33.15283 127.7731 56.287818 111.82251 64.15625 111.8125 C 72.024682 111.80249 95.202691 127.69555 101.5625 123.0625 C 107.92231 118.42945 99.890544 91.486414 102.3125 84 C 104.73446 76.513583 127.03475 59.38648 124.59375 51.90625 C 122.15275 44.426021 94.027829 43.741849 87.65625 39.125 C 81.28467 34.508152 71.899685 7.9899879 64.03125 8 z M 64.03125 11.90625 C 64.208046 12.045423 65.56776 12.712264 67.15625 14.65625 C 68.97167 16.877947 71.031426 20.210059 73.0625 23.75 C 75.093573 27.28994 77.113982 31.048819 79.09375 34.3125 C 81.073519 37.576182 82.75512 40.328991 85.40625 42.25 C 88.057376 44.171009 91.18831 44.91637 94.90625 45.78125 C 98.624192 46.646129 102.81606 47.391152 106.8125 48.21875 C 110.80894 49.046347 114.60465 49.966787 117.28125 51 C 119.62327 51.904061 120.71845 53.000764 120.90625 53.125 C 120.82618 53.333062 120.57672 54.794782 119.21875 56.90625 C 117.66679 59.319356 115.1453 62.318181 112.40625 65.34375 C 109.66721 68.369316 106.71091 71.452346 104.21875 74.34375 C 101.72659 77.235155 99.632744 79.697501 98.625 82.8125 C 97.617256 85.927495 97.892393 89.134266 98.21875 92.9375 C 98.545107 96.740738 99.114622 100.97466 99.5625 105.03125 C 100.01038 109.08783 100.31178 112.97888 100.15625 115.84375 C 100.02016 118.35052 99.34151 119.69095 99.28125 119.90625 C 99.057443 119.89786 97.552762 120.17027 95.125 119.53125 C 92.350417 118.80093 88.723899 117.29504 85 115.625 C 81.276103 113.95497 77.426259 112.10169 73.90625 110.625 C 70.386242 109.1483 67.4302 107.93334 64.15625 107.9375 C 60.882303 107.94167 57.891241 109.1706 54.375 110.65625 C 50.858761 112.1419 47.032137 114.00799 43.3125 115.6875 C 39.592862 117.367 35.960216 118.85638 33.1875 119.59375 C 30.761373 120.23895 29.286908 119.99088 29.0625 120 C 29.004012 119.7864 28.29872 118.4439 28.15625 115.9375 C 27.993428 113.07303 28.281199 109.18271 28.71875 105.125 C 29.156299 101.0673 29.714573 96.835302 30.03125 93.03125 C 30.347928 89.227198 30.609418 85.987425 29.59375 82.875 C 28.578082 79.762573 26.468263 77.322553 23.96875 74.4375 C 21.469238 71.552452 18.527988 68.487339 15.78125 65.46875 C 13.034512 62.450158 10.495601 59.471649 8.9375 57.0625 C 7.5741618 54.954496 7.3592053 53.457399 7.28125 53.25 C 7.2962039 53.337785 8.2681026 52.126785 10.84375 51.125 C 13.517705 50.084977 17.34943 49.150265 21.34375 48.3125 C 25.33807 47.474737 29.534272 46.749339 33.25 45.875 C 36.96573 45.000663 40.103767 44.24025 42.75 42.3125 C 45.396234 40.384748 47.059794 37.612458 49.03125 34.34375 C 51.002705 31.075042 53.009191 27.326347 55.03125 23.78125 C 57.053308 20.236153 59.096493 16.88256 60.90625 14.65625 C 62.489787 12.708229 63.857465 12.044552 64.03125 11.90625 z "
|
||||
id="path2304" />
|
||||
<path
|
||||
style="fill:url(#linearGradient3800);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter3838)"
|
||||
d="M 60.98796,9.471226 C 62.846491,8.2143022 64.889907,8.0204702 67.219338,9.471226 L 64.037358,15.614216 L 60.98796,9.471226 z"
|
||||
id="path3409"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="opacity:1;fill:url(#linearGradient3315);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:14.80892944;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1"
|
||||
d="M 64.039064,11.90625 C 63.865274,12.044552 62.497594,12.708229 60.914064,14.65625 C 59.104304,16.88256 57.061124,20.236153 55.039064,23.78125 C 53.017004,27.326347 51.010514,31.075042 49.039064,34.34375 C 47.067604,37.612458 45.404044,40.384748 42.757814,42.3125 C 40.111574,44.24025 36.973544,45.000663 33.257814,45.875 C 29.542084,46.749339 25.345884,47.474737 21.351564,48.3125 C 17.357244,49.150265 13.525514,50.084977 10.851564,51.125 C 8.2759131,52.126785 7.3040131,53.337785 7.2890631,53.25 C 7.3670131,53.457399 7.5819731,54.954496 8.9453131,57.0625 C 10.503414,59.471649 13.042324,62.450158 15.789064,65.46875 C 18.535804,68.487339 21.477054,71.552452 23.976564,74.4375 C 26.476074,77.322553 28.585894,79.762573 29.601564,82.875 C 29.865144,83.682722 30.019904,84.511238 30.132814,85.34375 C 32.540654,85.431079 34.961934,85.5 37.414064,85.5 C 64.456484,85.5 88.974124,80.107134 106.91406,71.34375 C 108.71383,69.370041 110.60784,67.338911 112.41406,65.34375 C 115.15311,62.318181 117.67459,59.319356 119.22656,56.90625 C 120.58453,54.794782 120.83398,53.333062 120.91406,53.125 C 120.72626,53.000764 119.63107,51.904061 117.28906,51 C 114.61246,49.966787 110.81674,49.046347 106.82031,48.21875 C 102.82387,47.391152 98.631994,46.646129 94.914064,45.78125 C 91.196124,44.91637 88.065184,44.171009 85.414064,42.25 C 82.762934,40.328991 81.081334,37.576182 79.101564,34.3125 C 77.121794,31.048819 75.101384,27.28994 73.070314,23.75 C 71.039234,20.210059 68.979484,16.877947 67.164064,14.65625 C 65.575574,12.712264 64.215854,12.045423 64.039064,11.90625 z"
|
||||
id="path2910" />
|
||||
<g
|
||||
id="g3339"
|
||||
transform="translate(-132.29928,0)">
|
||||
<path
|
||||
id="path3317"
|
||||
d="M 196.34375,11.90625 C 196.16996,12.044552 194.80228,12.708229 193.21875,14.65625 C 191.40899,16.88256 189.36581,20.236153 187.34375,23.78125 C 185.32169,27.326347 183.3152,31.075042 181.34375,34.34375 C 179.37229,37.612458 177.70873,40.384748 175.0625,42.3125 C 172.41626,44.24025 169.27823,45.000663 165.5625,45.875 C 161.84677,46.749339 157.65057,47.474737 153.65625,48.3125 C 149.66193,49.150265 145.8302,50.084977 143.15625,51.125 C 140.5806,52.126785 139.6087,53.337785 139.59375,53.25 C 139.62377,53.329884 139.71528,53.638731 139.84375,54.0625 C 140.2595,53.69998 141.25985,52.862595 143.15625,52.125 C 145.8302,51.084977 149.66193,50.150265 153.65625,49.3125 C 157.65057,48.474737 161.84677,47.749339 165.5625,46.875 C 169.27823,46.000663 172.41626,45.24025 175.0625,43.3125 C 177.70873,41.384748 179.37229,38.612458 181.34375,35.34375 C 183.3152,32.075042 185.32169,28.326347 187.34375,24.78125 C 189.36581,21.236153 191.40899,17.88256 193.21875,15.65625 C 194.80228,13.708229 196.16996,13.044552 196.34375,12.90625 C 196.52054,13.045423 197.88026,13.712264 199.46875,15.65625 C 201.28417,17.877947 203.34392,21.210059 205.375,24.75 C 207.40607,28.28994 209.42648,32.048819 211.40625,35.3125 C 213.38602,38.576182 215.06762,41.328991 217.71875,43.25 C 220.36987,45.171009 223.50081,45.91637 227.21875,46.78125 C 230.93668,47.646129 235.12856,48.391152 239.125,49.21875 C 243.12143,50.046347 246.91715,50.966787 249.59375,52 C 251.51448,52.74144 252.56925,53.579608 253,53.9375 C 253.13371,53.522484 253.18802,53.204851 253.21875,53.125 C 253.03095,53.000764 251.93576,51.904061 249.59375,51 C 246.91715,49.966787 243.12143,49.046347 239.125,48.21875 C 235.12856,47.391152 230.93668,46.646129 227.21875,45.78125 C 223.50081,44.91637 220.36987,44.171009 217.71875,42.25 C 215.06762,40.328991 213.38602,37.576182 211.40625,34.3125 C 209.42648,31.048819 207.40607,27.28994 205.375,23.75 C 203.34392,20.210059 201.28417,16.877947 199.46875,14.65625 C 197.88026,12.712264 196.52054,12.045423 196.34375,11.90625 z"
|
||||
style="opacity:1;fill:url(#linearGradient3369);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:14.80892944000000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1" />
|
||||
<path
|
||||
sodipodi:nodetypes="cscsscsccscsscsc"
|
||||
id="path3325"
|
||||
d="M 246.78125,49.937636 C 247.42469,50.142466 248.03845,50.379526 248.59375,50.593886 C 250.93576,51.497946 252.03095,52.594656 252.21875,52.718886 C 252.13867,52.926956 251.88922,54.388676 250.53125,56.500136 C 248.97928,58.913246 246.4578,61.912066 243.71875,64.937636 C 241.91253,66.932796 240.01852,68.963926 238.21875,70.937636 C 220.27881,79.701026 195.76117,85.093886 168.71875,85.093886 C 166.59433,85.093886 164.49568,85.039506 162.40625,84.968886 C 162.4184,85.051736 162.42625,85.135936 162.4375,85.218886 C 164.84534,85.306216 167.26662,85.375136 169.71875,85.375136 C 196.76117,85.375136 221.27881,79.982276 239.21875,71.218886 C 241.01852,69.245176 242.91253,67.214046 244.71875,65.218886 C 247.4578,62.193316 249.97928,59.194496 251.53125,56.781386 C 252.88922,54.669926 253.13867,53.208206 253.21875,53.000136 C 253.03095,52.875906 251.93576,51.779196 249.59375,50.875136 C 248.75868,50.552786 247.80629,50.238636 246.78125,49.937636 z"
|
||||
style="opacity:1;fill:url(#linearGradient3345);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:14.80892944;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:url(#linearGradient3850);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;opacity:0.77153558000000000;filter:url(#filter3928)"
|
||||
d="M 25.190679,119.77989 C 26.414679,122.74238 27.241162,124.11897 31.289475,123.31542 L 30.638356,120.21008 L 29.079766,120.3986 L 28.261711,118.57341 L 25.190679,119.77989 z"
|
||||
id="path3842"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
<path
|
||||
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient3385);stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1;filter:url(#filter3391)"
|
||||
d="M 64.125001,11.90625 C 63.951211,12.044552 62.583531,12.708229 61.000001,14.65625 C 59.190241,16.88256 57.147061,20.236153 55.125001,23.78125 C 53.102941,27.326347 51.096451,31.075042 49.125001,34.34375 C 47.153541,37.612458 45.489981,40.384748 42.843751,42.3125 C 40.197511,44.24025 37.059481,45.000663 33.343751,45.875 C 29.628021,46.749339 25.431821,47.474737 21.437501,48.3125 C 17.443181,49.150265 13.611451,50.084977 10.937501,51.125 C 8.3618506,52.126785 7.3899506,53.337785 7.3750006,53.25 C 7.4529506,53.457399 7.6679106,54.954496 9.0312506,57.0625 C 10.589351,59.471649 13.128261,62.450158 15.875001,65.46875 C 18.621741,68.487339 21.562991,71.552452 24.062501,74.4375 C 26.562011,77.322553 28.671831,79.762573 29.687501,82.875 C 30.703171,85.987425 30.441681,89.227198 30.125001,93.03125 C 29.808321,96.835302 29.250051,101.0673 28.812501,105.125 C 28.374951,109.18271 28.087181,113.07303 28.250001,115.9375 C 28.392471,118.4439 29.097761,119.7864 29.156251,120 C 29.380661,119.99088 30.855121,120.23895 33.281251,119.59375 C 36.053961,118.85638 39.686611,117.367 43.406251,115.6875 C 47.125881,114.00799 50.952511,112.1419 54.468751,110.65625 C 57.984991,109.1706 60.976051,107.94167 64.250001,107.9375 C 67.523951,107.93334 70.479991,109.1483 74.000001,110.625 C 77.520011,112.10169 81.369851,113.95497 85.093751,115.625 C 88.817651,117.29504 92.444151,118.80093 95.218751,119.53125 C 97.646511,120.17027 99.151181,119.89786 99.375001,119.90625 C 99.435261,119.69095 100.1139,118.35052 100.25,115.84375 C 100.40553,112.97888 100.10412,109.08783 99.656251,105.03125 C 99.208371,100.97466 98.638841,96.740738 98.312501,92.9375 C 97.986141,89.134266 97.710991,85.927495 98.718751,82.8125 C 99.726491,79.697501 101.82033,77.235155 104.3125,74.34375 C 106.80466,71.452346 109.76095,68.369316 112.5,65.34375 C 115.23905,62.318181 117.76053,59.319356 119.3125,56.90625 C 120.67047,54.794782 120.91992,53.333062 121,53.125 C 120.8122,53.000764 119.71701,51.904061 117.375,51 C 114.6984,49.966787 110.90268,49.046347 106.90625,48.21875 C 102.90981,47.391152 98.717931,46.646129 95.000001,45.78125 C 91.282061,44.91637 88.151121,44.171009 85.500001,42.25 C 82.848871,40.328991 81.167271,37.576182 79.187501,34.3125 C 77.207731,31.048819 75.187321,27.28994 73.156251,23.75 C 71.125171,20.210059 69.065421,16.877947 67.250001,14.65625 C 65.661511,12.712264 64.301791,12.045423 64.125001,11.90625 z"
|
||||
id="path3375"
|
||||
sodipodi:nodetypes="cssssssssssssssscssssssscssssssscsssssssc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cssssssssssssssscssssssscssssssscsssssssc"
|
||||
id="path3395"
|
||||
d="M 64.125001,11.90625 C 63.951211,12.044552 62.583531,12.708229 61.000001,14.65625 C 59.190241,16.88256 57.147061,20.236153 55.125001,23.78125 C 53.102941,27.326347 51.096451,31.075042 49.125001,34.34375 C 47.153541,37.612458 45.489981,40.384748 42.843751,42.3125 C 40.197511,44.24025 37.059481,45.000663 33.343751,45.875 C 29.628021,46.749339 25.431821,47.474737 21.437501,48.3125 C 17.443181,49.150265 13.611451,50.084977 10.937501,51.125 C 8.3618506,52.126785 7.3899506,53.337785 7.3750006,53.25 C 7.4529506,53.457399 7.6679106,54.954496 9.0312506,57.0625 C 10.589351,59.471649 13.128261,62.450158 15.875001,65.46875 C 18.621741,68.487339 21.562991,71.552452 24.062501,74.4375 C 26.562011,77.322553 28.671831,79.762573 29.687501,82.875 C 30.703171,85.987425 30.441681,89.227198 30.125001,93.03125 C 29.808321,96.835302 29.250051,101.0673 28.812501,105.125 C 28.374951,109.18271 28.087181,113.07303 28.250001,115.9375 C 28.392471,118.4439 29.097761,119.7864 29.156251,120 C 29.380661,119.99088 30.855121,120.23895 33.281251,119.59375 C 36.053961,118.85638 39.686611,117.367 43.406251,115.6875 C 47.125881,114.00799 50.952511,112.1419 54.468751,110.65625 C 57.984991,109.1706 60.976051,107.94167 64.250001,107.9375 C 67.523951,107.93334 70.479991,109.1483 74.000001,110.625 C 77.520011,112.10169 81.369851,113.95497 85.093751,115.625 C 88.817651,117.29504 92.444151,118.80093 95.218751,119.53125 C 97.646511,120.17027 99.151181,119.89786 99.375001,119.90625 C 99.435261,119.69095 100.1139,118.35052 100.25,115.84375 C 100.40553,112.97888 100.10412,109.08783 99.656251,105.03125 C 99.208371,100.97466 98.638841,96.740738 98.312501,92.9375 C 97.986141,89.134266 97.710991,85.927495 98.718751,82.8125 C 99.726491,79.697501 101.82033,77.235155 104.3125,74.34375 C 106.80466,71.452346 109.76095,68.369316 112.5,65.34375 C 115.23905,62.318181 117.76053,59.319356 119.3125,56.90625 C 120.67047,54.794782 120.91992,53.333062 121,53.125 C 120.8122,53.000764 119.71701,51.904061 117.375,51 C 114.6984,49.966787 110.90268,49.046347 106.90625,48.21875 C 102.90981,47.391152 98.717931,46.646129 95.000001,45.78125 C 91.282061,44.91637 88.151121,44.171009 85.500001,42.25 C 82.848871,40.328991 81.167271,37.576182 79.187501,34.3125 C 77.207731,31.048819 75.187321,27.28994 73.156251,23.75 C 71.125171,20.210059 69.065421,16.877947 67.250001,14.65625 C 65.661511,12.712264 64.301791,12.045423 64.125001,11.90625 z"
|
||||
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient3385);stroke-width:0.6;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1;filter:url(#filter3401)" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccccc"
|
||||
id="path3932"
|
||||
d="M 25.190679,119.77989 C 26.414679,122.74238 27.241162,124.11897 31.289475,123.31542 L 30.638356,120.21008 L 29.079766,120.3986 L 28.261711,118.57341 L 25.190679,119.77989 z"
|
||||
style="opacity:0.7715356;fill:url(#linearGradient3934);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter3928)"
|
||||
transform="matrix(-1,0,0,1,128.10515,0)" />
|
||||
<path
|
||||
style="opacity:1;fill:url(#radialGradient3956);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:14.80851269000000059;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4;stroke-opacity:1"
|
||||
d="M 26.03125 94.25 C 24.983755 105.1142 22.21942 119.85075 26.78125 123.15625 C 33.15283 127.7731 56.287818 111.82251 64.15625 111.8125 C 72.024682 111.80249 95.202691 127.69555 101.5625 123.0625 C 106.10279 119.75495 103.30815 105.10184 102.21875 94.25 L 98.34375 94.25 C 98.677864 97.707156 99.164649 101.42777 99.5625 105.03125 C 100.01038 109.08783 100.31178 112.97888 100.15625 115.84375 C 100.02016 118.35052 99.34151 119.69095 99.28125 119.90625 C 99.057443 119.89786 97.552762 120.17027 95.125 119.53125 C 92.350417 118.80093 88.723899 117.29504 85 115.625 C 81.276103 113.95497 77.426259 112.10169 73.90625 110.625 C 70.386242 109.1483 67.4302 107.93334 64.15625 107.9375 C 60.882303 107.94167 57.891241 109.1706 54.375 110.65625 C 50.858761 112.1419 47.032137 114.00799 43.3125 115.6875 C 39.592862 117.367 35.960216 118.85638 33.1875 119.59375 C 30.761373 120.23895 29.286908 119.99088 29.0625 120 C 29.004012 119.7864 28.29872 118.4439 28.15625 115.9375 C 27.993428 113.07303 28.281199 109.18271 28.71875 105.125 C 29.110886 101.48845 29.580993 97.733027 29.90625 94.25 L 26.03125 94.25 z "
|
||||
id="path3936" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 36 KiB |
BIN
resources/images/news/ajc.png
Normal file
BIN
resources/images/news/ajc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/rating.png
Normal file
BIN
resources/images/rating.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.7 KiB |
116
resources/jacket/stylesheet.css
Normal file
116
resources/jacket/stylesheet.css
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
** Book Jacket generation
|
||||
**
|
||||
** The template for Book Jackets is template.xhtml
|
||||
** This CSS is inserted into the generated HTML at conversion time
|
||||
**
|
||||
** Users can control parts of the presentation of a generated book jacket by
|
||||
** editing this file and template.xhtml
|
||||
**
|
||||
** The general form of a generated Book Jacket:
|
||||
**
|
||||
** Title
|
||||
** Series: series [series_index]
|
||||
** Published: year_of_publication
|
||||
** Rating: #_of_stars
|
||||
** Tags: tag1, tag2, tag3 ...
|
||||
**
|
||||
** Comments
|
||||
**
|
||||
** If a book does not have Series information, a date of publication, a rating or tags
|
||||
** the corresponding row is automatically removed from the generated book jacket.
|
||||
*/
|
||||
|
||||
/*
|
||||
** Banner
|
||||
** Only affects EPUB, kindle ignores this type of formatting
|
||||
*/
|
||||
.cbj_banner {
|
||||
background: #eee;
|
||||
border: thin solid black;
|
||||
margin: 1em;
|
||||
padding: 1em;
|
||||
-webkit-border-radius:8px;
|
||||
}
|
||||
|
||||
/*
|
||||
** Title
|
||||
*/
|
||||
.cbj_title {
|
||||
font-size: x-large;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/*
|
||||
** Table containing Series, Publication Year, Rating and Tags
|
||||
*/
|
||||
table.cbj_header {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*
|
||||
** General formatting for banner labels
|
||||
*/
|
||||
table.cbj_header td.cbj_label {
|
||||
font-family: sans-serif;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
/*
|
||||
** General formatting for banner content
|
||||
*/
|
||||
table.cbj_header td.cbj_content {
|
||||
font-family: sans-serif;
|
||||
text-align: left;
|
||||
width:60%;
|
||||
}
|
||||
|
||||
/*
|
||||
** To skip a banner item (Series|Published|Rating|Tags),
|
||||
** edit the appropriate CSS rule below.
|
||||
*/
|
||||
table.cbj_header tr.cbj_series {
|
||||
/* Uncomment the next line to remove 'Series' from banner section */
|
||||
/* display:none; */
|
||||
}
|
||||
|
||||
table.cbj_header tr.cbj_pubdate {
|
||||
/* Uncomment the next line to remove 'Published' from banner section */
|
||||
/* display:none; */
|
||||
}
|
||||
|
||||
table.cbj_header tr.cbj_rating {
|
||||
/* Uncomment the next line to remove 'Rating' from banner section */
|
||||
/* display:none; */
|
||||
}
|
||||
|
||||
table.cbj_header tr.cbj_tags {
|
||||
/* Uncomment the next line to remove 'Tags' from banner section */
|
||||
/* display:none; */
|
||||
}
|
||||
|
||||
hr {
|
||||
/* This rule controls formatting for any hr elements contained in the jacket */
|
||||
border-top: 0px solid white;
|
||||
border-right: 0px solid white;
|
||||
border-bottom: 2px solid black;
|
||||
border-left: 0px solid white;
|
||||
margin-left: 10%;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.cbj_footer {
|
||||
font-family: sans-serif;
|
||||
font-size: small;
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
.cbj_smallcaps {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.cbj_comments {
|
||||
font-family: sans-serif;
|
||||
}
|
34
resources/jacket/template.xhtml
Normal file
34
resources/jacket/template.xhtml
Normal file
@ -0,0 +1,34 @@
|
||||
<html xmlns="{xmlns}">
|
||||
<head>
|
||||
<title>{title_str}</title>
|
||||
<meta name="calibre-content" content="jacket"/>
|
||||
<style type="text/css" media="screen">{css}</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cbj_banner">
|
||||
<div class="cbj_title">{title}</div>
|
||||
<table class="cbj_header">
|
||||
<tr class="cbj_series">
|
||||
<td class="cbj_label">{series_label}:</td>
|
||||
<td class="cbj_content">{series}</td>
|
||||
</tr>
|
||||
<tr class="cbj_pubdate">
|
||||
<td class="cbj_label">{pubdate_label}:</td>
|
||||
<td class="cbj_content">{pubdate}</td>
|
||||
</tr>
|
||||
<tr class="cbj_rating">
|
||||
<td class="cbj_label">{rating_label}:</td>
|
||||
<td class="cbj_content">{rating}</td>
|
||||
</tr>
|
||||
<tr class="cbj_tags">
|
||||
<td class="cbj_label">{tags_label}:</td>
|
||||
<td class="cbj_content">{tags}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="cbj_footer">{footer}</div>
|
||||
</div>
|
||||
<hr class="cbj_kindle_banner_hr" />
|
||||
<div class="cbj_comments">{comments}</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,7 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
__copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
www.adventuregamers.com
|
||||
'''
|
||||
@ -10,14 +8,11 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdventureGamers(BasicNewsRecipe):
|
||||
title = u'Adventure Gamers'
|
||||
language = 'en'
|
||||
|
||||
language = 'en'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Adventure games portal'
|
||||
publisher = 'Adventure Gamers'
|
||||
category = 'news, games, adventure, technology'
|
||||
language = 'en'
|
||||
|
||||
oldest_article = 10
|
||||
delay = 10
|
||||
max_articles_per_feed = 100
|
||||
@ -26,14 +21,25 @@ class AdventureGamers(BasicNewsRecipe):
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
INDEX = u'http://www.adventuregamers.com'
|
||||
extra_css = """
|
||||
.pageheader_type{font-size: x-large; font-weight: bold; color: #828D74}
|
||||
.pageheader_title{font-size: xx-large; color: #394128}
|
||||
.pageheader_byline{font-size: small; font-weight: bold; color: #394128}
|
||||
.score_bg {display: inline; width: 100%; margin-bottom: 2em}
|
||||
.score_column_1{ padding-left: 10px; font-size: small; width: 50%}
|
||||
.score_column_2{ padding-left: 10px; font-size: small; width: 50%}
|
||||
.score_column_3{ padding-left: 10px; font-size: small; width: 50%}
|
||||
.score_header{font-size: large; color: #50544A}
|
||||
.bodytext{display: block}
|
||||
body{font-family: Helvetica,Arial,sans-serif}
|
||||
"""
|
||||
|
||||
html2lrf_options = [
|
||||
'--comment', description
|
||||
, '--category', category
|
||||
, '--publisher', publisher
|
||||
]
|
||||
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':'content_middle'})
|
||||
@ -45,6 +51,7 @@ class AdventureGamers(BasicNewsRecipe):
|
||||
]
|
||||
|
||||
remove_tags_after = [dict(name='div', attrs={'class':'toolbar_fat'})]
|
||||
remove_attributes = ['width','height']
|
||||
|
||||
feeds = [(u'Articles', u'http://feeds2.feedburner.com/AdventureGamers')]
|
||||
|
||||
@ -66,12 +73,12 @@ class AdventureGamers(BasicNewsRecipe):
|
||||
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
mtag = '<meta http-equiv="Content-Language" content="en-US"/>\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>'
|
||||
soup.head.insert(0,mtag)
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
for item in soup.findAll('div', attrs={'class':'floatright'}):
|
||||
item.extract()
|
||||
self.append_page(soup, soup.body, 3)
|
||||
pager = soup.find('div',attrs={'class':'toolbar_fat'})
|
||||
if pager:
|
||||
pager.extract()
|
||||
return soup
|
||||
return self.adeify_images(soup)
|
||||
|
@ -10,12 +10,31 @@ class AdvancedUserRecipe1282101454(BasicNewsRecipe):
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'
|
||||
|
||||
masthead_url = 'http://gawand.org/wp-content/uploads/2010/06/ajc-logo.gif'
|
||||
extra_css = '''
|
||||
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||
'''
|
||||
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'id':['cxArticleContent']})
|
||||
,dict(attrs={'id':['cxArticleText','cxArticleBodyText']})
|
||||
dict(name='div', attrs={'class':['cxArticleHeader']})
|
||||
,dict(attrs={'id':['cxArticleText']})
|
||||
]
|
||||
|
||||
|
||||
remove_tags = [
|
||||
dict(name='div' , attrs={'class':'cxArticleList' })
|
||||
,dict(name='div' , attrs={'class':'cxFeedTease' })
|
||||
,dict(name='div' , attrs={'class':'cxElementEnlarge' })
|
||||
,dict(name='div' , attrs={'id':'cxArticleTools' })
|
||||
]
|
||||
|
||||
|
||||
|
||||
feeds = [
|
||||
('Breaking News', 'http://www.ajc.com/genericList-rss.do?source=61499'),
|
||||
# -------------------------------------------------------------------
|
||||
@ -23,7 +42,7 @@ class AdvancedUserRecipe1282101454(BasicNewsRecipe):
|
||||
# read by simply removing the pound sign from it. I currently have it
|
||||
# set to only get the Cobb area
|
||||
# --------------------------------------------------------------------
|
||||
('Atlanta & Fulton', 'http://www.ajc.com/section-rss.do?source=atlanta'),
|
||||
#('Atlanta & Fulton', 'http://www.ajc.com/section-rss.do?source=atlanta'),
|
||||
#('Clayton', 'http://www.ajc.com/section-rss.do?source=clayton'),
|
||||
#('DeKalb', 'http://www.ajc.com/section-rss.do?source=dekalb'),
|
||||
#('Gwinnett', 'http://www.ajc.com/section-rss.do?source=gwinnett'),
|
||||
@ -41,7 +60,7 @@ class AdvancedUserRecipe1282101454(BasicNewsRecipe):
|
||||
# but again
|
||||
# You can enable which ever team you like by removing the pound sign
|
||||
# ------------------------------------------------------------------------
|
||||
('Sports News', 'http://www.ajc.com/genericList-rss.do?source=61510'),
|
||||
#('Sports News', 'http://www.ajc.com/genericList-rss.do?source=61510'),
|
||||
#('Braves', 'http://www.ajc.com/genericList-rss.do?source=61457'),
|
||||
('Falcons', 'http://www.ajc.com/genericList-rss.do?source=61458'),
|
||||
#('Hawks', 'http://www.ajc.com/genericList-rss.do?source=61522'),
|
||||
@ -52,11 +71,16 @@ class AdvancedUserRecipe1282101454(BasicNewsRecipe):
|
||||
('Music', 'http://www.accessatlanta.com/section-rss.do?source=music'),
|
||||
]
|
||||
|
||||
def postprocess_html(self, soup, first):
|
||||
for credit_tag in soup.findAll('span', attrs={'class':['imageCredit rightFloat']}):
|
||||
credit_tag.name ='p'
|
||||
|
||||
return soup
|
||||
|
||||
#def print_version(self, url):
|
||||
# return url.partition('?')[0] +'?printArticle=y'
|
||||
|
||||
|
||||
|
||||
def print_version(self, url):
|
||||
return url.partition('?')[0] +'?printArticle=y'
|
||||
|
||||
|
||||
|
||||
|
125
resources/recipes/brand_eins.recipe
Normal file
125
resources/recipes/brand_eins.recipe
Normal file
@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Constantin Hofstetter <consti at consti.de>'
|
||||
__version__ = '0.95'
|
||||
|
||||
''' http://brandeins.de - Wirtschaftsmagazin '''
|
||||
import re
|
||||
import string
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class BrandEins(BasicNewsRecipe):
|
||||
|
||||
title = u'Brand Eins'
|
||||
__author__ = 'Constantin Hofstetter'
|
||||
description = u'Wirtschaftsmagazin'
|
||||
publisher ='brandeins.de'
|
||||
category = 'politics, business, wirtschaft, Germany'
|
||||
use_embedded_content = False
|
||||
lang = 'de-DE'
|
||||
no_stylesheets = True
|
||||
encoding = 'utf-8'
|
||||
language = 'de'
|
||||
|
||||
# 2 is the last full magazine (default)
|
||||
# 1 is the newest (but not full)
|
||||
# 3 is one before 2 etc.
|
||||
which_ausgabe = 2
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'theContent'}), dict(name='div', attrs={'id':'sidebar'}), dict(name='div', attrs={'class':'intro'}), dict(name='p', attrs={'class':'bodytext'}), dict(name='div', attrs={'class':'single_image'})]
|
||||
|
||||
'''
|
||||
brandeins.de
|
||||
'''
|
||||
|
||||
def postprocess_html(self, soup,first):
|
||||
|
||||
# Move the image of the sidebar right below the h3
|
||||
first_h3 = soup.find(name='div', attrs={'id':'theContent'}).find('h3')
|
||||
for imgdiv in soup.findAll(name='div', attrs={'class':'single_image'}):
|
||||
if len(first_h3.findNextSiblings('div', {'class':'intro'})) >= 1:
|
||||
# first_h3.parent.insert(2, imgdiv)
|
||||
first_h3.findNextSiblings('div', {'class':'intro'})[0].parent.insert(4, imgdiv)
|
||||
else:
|
||||
first_h3.parent.insert(2, imgdiv)
|
||||
|
||||
# Now, remove the sidebar
|
||||
soup.find(name='div', attrs={'id':'sidebar'}).extract()
|
||||
|
||||
# Remove the rating-image (stars) from the h3
|
||||
for img in first_h3.findAll(name='img'):
|
||||
img.extract()
|
||||
|
||||
# Mark the intro texts as italic
|
||||
for div in soup.findAll(name='div', attrs={'class':'intro'}):
|
||||
for p in div.findAll('p'):
|
||||
content = self.tag_to_string(p)
|
||||
new_p = "<p><i>"+ content +"</i></p>"
|
||||
p.replaceWith(new_p)
|
||||
|
||||
return soup
|
||||
|
||||
def parse_index(self):
|
||||
feeds = []
|
||||
|
||||
archive = "http://www.brandeins.de/archiv.html"
|
||||
|
||||
soup = self.index_to_soup(archive)
|
||||
latest_jahrgang = soup.findAll('div', attrs={'class': re.compile(r'\bjahrgang-latest\b') })[0].findAll('ul')[0]
|
||||
pre_latest_issue = latest_jahrgang.findAll('a')[len(latest_jahrgang.findAll('a'))-self.which_ausgabe]
|
||||
url = pre_latest_issue.get('href', False)
|
||||
# Get the title for the magazin - build it out of the title of the cover - take the issue and year;
|
||||
self.title = "Brand Eins "+ re.search(r"(?P<date>\d\d\/\d\d\d\d+)", pre_latest_issue.find('img').get('title', False)).group('date')
|
||||
url = 'http://brandeins.de/'+url
|
||||
|
||||
# url = "http://www.brandeins.de/archiv/magazin/tierisch.html"
|
||||
titles_and_articles = self.brand_eins_parse_latest_issue(url)
|
||||
if titles_and_articles:
|
||||
for title, articles in titles_and_articles:
|
||||
feeds.append((title, articles))
|
||||
return feeds
|
||||
|
||||
def brand_eins_parse_latest_issue(self, url):
|
||||
soup = self.index_to_soup(url)
|
||||
article_lists = [soup.find('div', attrs={'class':'subColumnLeft articleList'}), soup.find('div', attrs={'class':'subColumnRight articleList'})]
|
||||
|
||||
titles_and_articles = []
|
||||
current_articles = []
|
||||
chapter_title = "Editorial"
|
||||
self.log('Found Chapter:', chapter_title)
|
||||
|
||||
# Remove last list of links (thats just the impressum and the 'gewinnspiel')
|
||||
article_lists[1].findAll('ul')[len(article_lists[1].findAll('ul'))-1].extract()
|
||||
|
||||
for article_list in article_lists:
|
||||
for chapter in article_list.findAll('ul'):
|
||||
if len(chapter.findPreviousSiblings('h3')) >= 1:
|
||||
new_chapter_title = string.capwords(self.tag_to_string(chapter.findPreviousSiblings('h3')[0]))
|
||||
if new_chapter_title != chapter_title:
|
||||
titles_and_articles.append([chapter_title, current_articles])
|
||||
current_articles = []
|
||||
self.log('Found Chapter:', new_chapter_title)
|
||||
chapter_title = new_chapter_title
|
||||
for li in chapter.findAll('li'):
|
||||
a = li.find('a', href = True)
|
||||
if a is None:
|
||||
continue
|
||||
title = self.tag_to_string(a)
|
||||
url = a.get('href', False)
|
||||
if not url or not title:
|
||||
continue
|
||||
url = 'http://brandeins.de/'+url
|
||||
if len(a.parent.findNextSiblings('p')) >= 1:
|
||||
description = self.tag_to_string(a.parent.findNextSiblings('p')[0])
|
||||
else:
|
||||
description = ''
|
||||
|
||||
self.log('\t\tFound article:', title)
|
||||
self.log('\t\t\t', url)
|
||||
self.log('\t\t\t', description)
|
||||
|
||||
current_articles.append({'title': title, 'url': url, 'description': description, 'date':''})
|
||||
titles_and_articles.append([chapter_title, current_articles])
|
||||
return titles_and_articles
|
@ -20,6 +20,7 @@ class Danas(BasicNewsRecipe):
|
||||
encoding = 'utf-8'
|
||||
masthead_url = 'http://www.danas.rs/images/basic/danas.gif'
|
||||
language = 'sr'
|
||||
remove_javascript = True
|
||||
publication_type = 'newspaper'
|
||||
remove_empty_feeds = True
|
||||
extra_css = """ @font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
|
||||
@ -29,7 +30,8 @@ class Danas(BasicNewsRecipe):
|
||||
.antrfileText{border-left: 2px solid #999999; margin-left: 0.8em; padding-left: 1.2em;
|
||||
margin-bottom: 0; margin-top: 0} h2,.datum,.lokacija,.autor{font-size: small}
|
||||
.antrfileNaslov{border-left: 2px solid #999999; margin-left: 0.8em; padding-left: 1.2em;
|
||||
font-weight:bold; margin-bottom: 0; margin-top: 0} img{margin-bottom: 0.8em} """
|
||||
font-weight:bold; margin-bottom: 0; margin-top: 0} img{margin-bottom: 0.8em}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
@ -38,14 +40,26 @@ class Danas(BasicNewsRecipe):
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
||||
preprocess_regexps = [
|
||||
(re.compile(u'\u0110'), lambda match: u'\u00D0')
|
||||
,(re.compile(r'<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags".*?/>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
||||
,(re.compile(r'<st1:place.*?>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
||||
,(re.compile(r'</st1:place>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
||||
,(re.compile(r'<st1:city.*?>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
||||
,(re.compile(r'</st1:city>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
||||
,(re.compile(r'<st1:country-region.*?>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
||||
,(re.compile(r'</st1:country-region>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
||||
,(re.compile(r'<st1:state.*?>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
||||
,(re.compile(r'</st1:state>',re.DOTALL|re.IGNORECASE), lambda match: r'')
|
||||
]
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'left'})]
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':['width_1_4','metaClanka','baner']})
|
||||
,dict(name='div', attrs={'id':'comments'})
|
||||
,dict(name=['object','link','iframe'])
|
||||
,dict(name=['object','link','iframe','meta'])
|
||||
]
|
||||
remove_attributes = ['st']
|
||||
|
||||
feeds = [
|
||||
(u'Politika' , u'http://www.danas.rs/rss/rss.asp?column_id=27')
|
||||
@ -79,7 +93,13 @@ class Danas(BasicNewsRecipe):
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return self.adeify_images(soup)
|
||||
for item in soup.findAll('a'):
|
||||
if item.has_key('name'):
|
||||
item.extract()
|
||||
for item in soup.findAll('img'):
|
||||
if not item.has_key('alt'):
|
||||
item['alt'] = 'image'
|
||||
return soup
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '&action=print'
|
||||
|
@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
'''
|
||||
calibre recipe for slate.com
|
||||
'''
|
||||
@ -10,13 +11,12 @@ import re
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Comment, Tag
|
||||
|
||||
class PeriodicalNameHere(BasicNewsRecipe):
|
||||
class Slate(BasicNewsRecipe):
|
||||
# Method variables for customizing downloads
|
||||
title = 'Slate'
|
||||
description = 'A general-interest publication offering analysis and commentary about politics, news and culture.'
|
||||
__author__ = 'GRiker and Sujata Raman'
|
||||
max_articles_per_feed = 20
|
||||
oldest_article = 7.0
|
||||
__author__ = 'GRiker, Sujata Raman and Nick Redding'
|
||||
max_articles_per_feed = 100
|
||||
oldest_article = 14
|
||||
recursions = 0
|
||||
delay = 0
|
||||
simultaneous_downloads = 5
|
||||
@ -27,8 +27,11 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
encoding = None
|
||||
language = 'en'
|
||||
|
||||
|
||||
|
||||
slate_complete = True
|
||||
if slate_complete:
|
||||
title = 'Slate (complete)'
|
||||
else:
|
||||
title = 'Slate (weekly)'
|
||||
|
||||
# Method variables for customizing feed parsing
|
||||
summary_length = 250
|
||||
@ -50,8 +53,10 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
|
||||
# The second entry is for 'Big Money', which comes from a different site, uses different markup
|
||||
remove_tags = [dict(attrs={ 'id':['toolbox','recommend_tab','insider_ad_wrapper',
|
||||
'article_bottom_tools_cntr','fray_article_discussion', 'fray_article_links','bottom_sponsored_links','author_bio',
|
||||
'bizbox_links_bottom','ris_links_wrapper','BOXXLE']}),
|
||||
'article_bottom_tools_cntr','fray_article_discussion','fray_article_links','bottom_sponsored_links','author_bio',
|
||||
'bizbox_links_bottom','ris_links_wrapper','BOXXLE',
|
||||
'comments_button','add_comments_button','comments-to-fray','marriott_ad',
|
||||
'article_bottom_tools','recommend_tab2','fbog_article_bottom_cntr']}),
|
||||
dict(attrs={ 'id':['content-top','service-links-bottom','hed']}) ]
|
||||
|
||||
excludedDescriptionKeywords = ['Slate V','Twitter feed','podcast']
|
||||
@ -62,16 +67,15 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
extra_css = '''
|
||||
.h1_subhead{font-family:Arial; font-size:small; }
|
||||
h1{font-family:Verdana; font-size:large; }
|
||||
.byline {font-family:Georgia; margin-bottom: 0px; color: #660033;}
|
||||
.dateline {font-family:Arial; font-size: smaller; height: 0pt; color:#666666;}
|
||||
.byline {font-family:Georgia; margin-bottom: 0px; }
|
||||
.dateline {font-family:Arial; font-size: smaller; height: 0pt;}
|
||||
.imagewrapper {font-family:Verdana;font-size:x-small; }
|
||||
.source {font-family:Verdana; font-size:x-small;}
|
||||
.credit {font-family:Verdana; font-size: smaller;}
|
||||
#article_body {font-family:Verdana; }
|
||||
#content {font-family:Arial; }
|
||||
.caption{font-family:Verdana;font-style:italic; font-size:x-small;}
|
||||
h3{font-family:Arial; color:#666666; font-size:small}
|
||||
a{color:#0066CC;}
|
||||
h3{font-family:Arial; font-size:small}
|
||||
'''
|
||||
|
||||
# Local variables to extend class
|
||||
@ -89,32 +93,59 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
if isinstance(item, (NavigableString, CData)):
|
||||
strings.append(item.string)
|
||||
elif isinstance(item, Tag):
|
||||
res = self.tag_to_string(item)
|
||||
res = self.tag_to_string(item,use_alt=False)
|
||||
if res:
|
||||
strings.append(res)
|
||||
return strings
|
||||
|
||||
|
||||
def extract_sections(self):
|
||||
def extract_named_sections(self):
|
||||
soup = self.index_to_soup( self.baseURL )
|
||||
soup_top_stories = soup.find(True, attrs={'class':'tap2_topic entry-content'})
|
||||
soup_nav_bar = soup.find(True, attrs={'id':'nav'})
|
||||
briefing_nav = soup.find('li')
|
||||
briefing_url = briefing_nav.a['href']
|
||||
for section_nav in soup_nav_bar.findAll('li'):
|
||||
section_name = self.tag_to_string(section_nav,use_alt=False)
|
||||
self.section_dates.append(section_name)
|
||||
|
||||
soup = self.index_to_soup(briefing_url)
|
||||
|
||||
self.log("Briefing url = %s " % briefing_url)
|
||||
section_lists = soup.findAll('ul','view_links_list')
|
||||
|
||||
sections = []
|
||||
for section in section_lists :
|
||||
sections.append(section)
|
||||
return sections
|
||||
|
||||
|
||||
def extract_dated_sections(self):
|
||||
soup = self.index_to_soup( self.baseURL )
|
||||
soup_top_stories = soup.find(True, attrs={'id':'tap3_cntr'})
|
||||
if soup_top_stories:
|
||||
self.section_dates.append("Top Stories")
|
||||
self.log("SELECTION TOP STORIES %s" % "Top Stories")
|
||||
|
||||
soup = soup.find(True, attrs={'id':'toc_links_container'})
|
||||
|
||||
todays_section = soup.find(True, attrs={'class':'todaydateline'})
|
||||
self.section_dates.append(self.tag_to_string(todays_section,use_alt=False))
|
||||
self.log("SELECTION DATE %s" % self.tag_to_string(todays_section,use_alt=False))
|
||||
|
||||
older_section_dates = soup.findAll(True, attrs={'class':'maindateline'})
|
||||
for older_section in older_section_dates :
|
||||
self.section_dates.append(self.tag_to_string(older_section,use_alt=False))
|
||||
self.log("SELECTION DATE %s" % self.tag_to_string(older_section,use_alt=False))
|
||||
|
||||
if soup_top_stories:
|
||||
headline_stories = soup_top_stories.find('ul')
|
||||
headline_stories = soup_top_stories
|
||||
self.log("HAVE top_stories")
|
||||
else:
|
||||
headline_stories = None
|
||||
self.log("NO top_stories")
|
||||
section_lists = soup.findAll('ul')
|
||||
# Prepend the headlines to the first section
|
||||
if headline_stories:
|
||||
section_lists[0].insert(0,headline_stories)
|
||||
section_lists.insert(0,headline_stories)
|
||||
|
||||
sections = []
|
||||
for section in section_lists :
|
||||
@ -123,9 +154,8 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
|
||||
|
||||
def extract_section_articles(self, sections_html) :
|
||||
# Find the containers with section content
|
||||
soup = self.index_to_soup(str(sections_html))
|
||||
sections = soup.findAll('ul')
|
||||
# Find the containers with section content
|
||||
sections = sections_html
|
||||
|
||||
articles = {}
|
||||
key = None
|
||||
@ -135,10 +165,25 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
|
||||
# Get the section name
|
||||
if section.has_key('id') :
|
||||
self.log("PROCESSING SECTION id = %s" % section['id'])
|
||||
key = self.section_dates[i]
|
||||
if key.startswith("Pod"):
|
||||
continue
|
||||
if key.startswith("Blog"):
|
||||
continue
|
||||
articles[key] = []
|
||||
ans.append(key)
|
||||
elif self.slate_complete:
|
||||
key = self.section_dates[i]
|
||||
if key.startswith("Pod"):
|
||||
continue
|
||||
if key.startswith("Blog"):
|
||||
continue
|
||||
self.log("PROCESSING SECTION name = %s" % key)
|
||||
articles[key] = []
|
||||
ans.append(key)
|
||||
else :
|
||||
self.log("SECTION %d HAS NO id" % i);
|
||||
continue
|
||||
|
||||
# Get the section article_list
|
||||
@ -149,8 +194,10 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
bylines = self.tag_to_strings(article)
|
||||
url = article.a['href']
|
||||
title = bylines[0]
|
||||
full_title = self.tag_to_string(article)
|
||||
|
||||
full_title = self.tag_to_string(article,use_alt=False)
|
||||
#self.log("ARTICLE TITLE%s" % title)
|
||||
#self.log("ARTICLE FULL_TITLE%s" % full_title)
|
||||
#self.log("URL %s" % url)
|
||||
author = None
|
||||
description = None
|
||||
pubdate = None
|
||||
@ -181,7 +228,7 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
excluded = re.compile('|'.join(self.excludedDescriptionKeywords))
|
||||
found_excluded = excluded.search(description)
|
||||
if found_excluded :
|
||||
if self.verbose : self.log(" >>> skipping %s (description keyword exclusion: %s) <<<\n" % (title, found_excluded.group(0)))
|
||||
self.log(" >>> skipping %s (description keyword exclusion: %s) <<<\n" % (title, found_excluded.group(0)))
|
||||
continue
|
||||
|
||||
# Skip articles whose title contain excluded keywords
|
||||
@ -190,7 +237,7 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
#self.log("evaluating full_title: %s" % full_title)
|
||||
found_excluded = excluded.search(full_title)
|
||||
if found_excluded :
|
||||
if self.verbose : self.log(" >>> skipping %s (title keyword exclusion: %s) <<<\n" % (title, found_excluded.group(0)))
|
||||
self.log(" >>> skipping %s (title keyword exclusion: %s) <<<\n" % (title, found_excluded.group(0)))
|
||||
continue
|
||||
|
||||
# Skip articles whose author contain excluded keywords
|
||||
@ -198,7 +245,7 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
excluded = re.compile('|'.join(self.excludedAuthorKeywords))
|
||||
found_excluded = excluded.search(author)
|
||||
if found_excluded :
|
||||
if self.verbose : self.log(" >>> skipping %s (author keyword exclusion: %s) <<<\n" % (title, found_excluded.group(0)))
|
||||
self.log(" >>> skipping %s (author keyword exclusion: %s) <<<\n" % (title, found_excluded.group(0)))
|
||||
continue
|
||||
|
||||
skip_this_article = False
|
||||
@ -206,6 +253,7 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
for article in articles[key] :
|
||||
if article['url'] == url :
|
||||
skip_this_article = True
|
||||
self.log("SKIPPING DUP %s" % url)
|
||||
break
|
||||
|
||||
if skip_this_article :
|
||||
@ -217,6 +265,8 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
articles[feed] = []
|
||||
articles[feed].append(dict(title=title, url=url, date=pubdate, description=description,
|
||||
author=author, content=''))
|
||||
#self.log("KEY %s" % feed)
|
||||
#self.log("APPENDED %s" % url)
|
||||
# Promote 'newspapers' to top
|
||||
for (i,article) in enumerate(articles[feed]) :
|
||||
if article['description'] is not None :
|
||||
@ -225,32 +275,6 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
|
||||
|
||||
ans = [(key, articles[key]) for key in ans if articles.has_key(key)]
|
||||
ans = self.remove_duplicates(ans)
|
||||
return ans
|
||||
|
||||
def flatten_document(self, ans):
|
||||
flat_articles = []
|
||||
for (i,section) in enumerate(ans) :
|
||||
#self.log("flattening section %s: " % section[0])
|
||||
for article in section[1] :
|
||||
#self.log("moving %s to flat_articles[]" % article['title'])
|
||||
flat_articles.append(article)
|
||||
flat_section = ['All Articles', flat_articles]
|
||||
flat_ans = [flat_section]
|
||||
return flat_ans
|
||||
|
||||
def remove_duplicates(self, ans):
|
||||
# Return a stripped ans
|
||||
for (i,section) in enumerate(ans) :
|
||||
#self.log("section %s: " % section[0])
|
||||
for article in section[1] :
|
||||
#self.log("\t%s" % article['title'])
|
||||
#self.log("\looking for %s" % article['url'])
|
||||
for (j,subsequent_section) in enumerate(ans[i+1:]) :
|
||||
for (k,subsequent_article) in enumerate(subsequent_section[1]) :
|
||||
if article['url'] == subsequent_article['url'] :
|
||||
#self.log( "removing %s (%s) from %s" % (subsequent_article['title'], subsequent_article['url'], subsequent_section[0]) )
|
||||
del subsequent_section[1][k]
|
||||
return ans
|
||||
|
||||
def print_version(self, url) :
|
||||
@ -258,13 +282,22 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
|
||||
# Class methods
|
||||
def parse_index(self) :
|
||||
sections = self.extract_sections()
|
||||
if self.slate_complete:
|
||||
sections = self.extract_named_sections()
|
||||
else:
|
||||
sections = self.extract_dated_sections()
|
||||
section_list = self.extract_section_articles(sections)
|
||||
section_list = self.flatten_document(section_list)
|
||||
return section_list
|
||||
|
||||
def get_browser(self) :
|
||||
return BasicNewsRecipe.get_browser()
|
||||
def get_masthead_url(self):
|
||||
masthead = 'http://img.slate.com/images/redesign2008/slate_logo.gif'
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
try:
|
||||
br.open(masthead)
|
||||
except:
|
||||
self.log("\nMasthead unavailable")
|
||||
masthead = None
|
||||
return masthead
|
||||
|
||||
def stripAnchors(self,soup):
|
||||
body = soup.find('div',attrs={'id':['article_body','content']})
|
||||
@ -294,8 +327,8 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
excluded = re.compile('|'.join(self.excludedContentKeywords))
|
||||
found_excluded = excluded.search(str(soup))
|
||||
if found_excluded :
|
||||
print "no allowed content found, removing article"
|
||||
raise Exception('String error')
|
||||
print "No allowed content found, removing article"
|
||||
raise Exception('Rejected article')
|
||||
|
||||
# Articles from www.thebigmoney.com use different tagging for byline, dateline and body
|
||||
head = soup.find('head')
|
||||
@ -328,7 +361,6 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
dept_kicker = soup.find('div', attrs={'class':'department_kicker'})
|
||||
if dept_kicker is not None :
|
||||
kicker_strings = self.tag_to_strings(dept_kicker)
|
||||
#kicker = kicker_strings[2] + kicker_strings[3]
|
||||
kicker = ''.join(kicker_strings[2:])
|
||||
kicker = re.sub('\.','',kicker)
|
||||
h3Tag = Tag(soup, "h3")
|
||||
@ -336,25 +368,11 @@ class PeriodicalNameHere(BasicNewsRecipe):
|
||||
emTag.insert(0,NavigableString(kicker))
|
||||
h3Tag.insert(0, emTag)
|
||||
dept_kicker.replaceWith(h3Tag)
|
||||
else:
|
||||
self.log("No kicker--return null")
|
||||
return None
|
||||
|
||||
# Change <h1> to <h2>
|
||||
headline = soup.find("h1")
|
||||
tag = headline.find("span")
|
||||
tag.name = 'div'
|
||||
|
||||
if headline is not None :
|
||||
h2tag = Tag(soup, "h2")
|
||||
h2tag['class'] = "headline"
|
||||
strs = self.tag_to_strings(headline)
|
||||
result = ''
|
||||
for (i,substr) in enumerate(strs) :
|
||||
result += substr
|
||||
if i < len(strs) -1 :
|
||||
result += '<br />'
|
||||
#h2tag.insert(0, result)
|
||||
#headline.replaceWith(h2tag)
|
||||
|
||||
# Fix up the concatenated byline and dateline
|
||||
# Fix up the concatenated byline and dateline
|
||||
byline = soup.find(True,attrs={'class':'byline'})
|
||||
if byline is not None :
|
||||
bylineTag = Tag(soup,'div')
|
||||
|
@ -248,6 +248,9 @@ class OutputProfile(Plugin):
|
||||
#: If True, the date is appended to the title of downloaded news
|
||||
periodical_date_in_title = True
|
||||
|
||||
#: The character used to represent a star in ratings
|
||||
ratings_char = u'*'
|
||||
|
||||
@classmethod
|
||||
def tags_to_string(cls, tags):
|
||||
return escape(', '.join(tags))
|
||||
@ -273,6 +276,7 @@ class iPadOutput(OutputProfile):
|
||||
'macros': {'border-width': '{length}|medium|thick|thin'}
|
||||
}
|
||||
]
|
||||
ratings_char = u'\u2605'
|
||||
touchscreen = True
|
||||
# touchscreen_news_css {{{
|
||||
touchscreen_news_css = u'''
|
||||
@ -553,10 +557,11 @@ class KindleOutput(OutputProfile):
|
||||
fsizes = [12, 12, 14, 16, 18, 20, 22, 24]
|
||||
supports_mobi_indexing = True
|
||||
periodical_date_in_title = False
|
||||
ratings_char = u'\u2605'
|
||||
|
||||
@classmethod
|
||||
def tags_to_string(cls, tags):
|
||||
return u'%s <br/><span style="color: white">%s</span>' % (', '.join(tags),
|
||||
return u'%s <br/><span style="color:white">%s</span>' % (', '.join(tags),
|
||||
'ttt '.join(tags)+'ttt ')
|
||||
|
||||
class KindleDXOutput(OutputProfile):
|
||||
|
@ -207,8 +207,8 @@ class ITUNES(DriverBase):
|
||||
for (j,p_book) in enumerate(self.update_list):
|
||||
if False:
|
||||
if isosx:
|
||||
self.log.info(" looking for %s" %
|
||||
str(p_book['lib_book'])[-9:])
|
||||
self.log.info(" looking for '%s' by %s uuid:%s" %
|
||||
(p_book['title'],p_book['author'], p_book['uuid']))
|
||||
elif iswindows:
|
||||
self.log.info(" looking for '%s' by %s (%s)" %
|
||||
(p_book['title'],p_book['author'], p_book['uuid']))
|
||||
@ -303,7 +303,7 @@ class ITUNES(DriverBase):
|
||||
this_book.device_collections = []
|
||||
this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None
|
||||
this_book.size = book.size()
|
||||
this_book.uuid = book.album()
|
||||
this_book.uuid = book.composer()
|
||||
# Hack to discover if we're running in GUI environment
|
||||
if self.report_progress is not None:
|
||||
this_book.thumbnail = self._generate_thumbnail(this_book.path, book)
|
||||
@ -732,15 +732,15 @@ class ITUNES(DriverBase):
|
||||
for path in paths:
|
||||
if DEBUG:
|
||||
self._dump_cached_book(self.cached_books[path], indent=2)
|
||||
self.log.info(" looking for '%s' by '%s' (%s)" %
|
||||
self.log.info(" looking for '%s' by '%s' uuid:%s" %
|
||||
(self.cached_books[path]['title'],
|
||||
self.cached_books[path]['author'],
|
||||
self.cached_books[path]['uuid']))
|
||||
|
||||
# Purge the booklist, self.cached_books, thumb cache
|
||||
for i,bl_book in enumerate(booklists[0]):
|
||||
if False:
|
||||
self.log.info(" evaluating '%s' by '%s' (%s)" %
|
||||
if DEBUG:
|
||||
self.log.info(" evaluating '%s' by '%s' uuid:%s" %
|
||||
(bl_book.title, bl_book.author,bl_book.uuid))
|
||||
|
||||
found = False
|
||||
@ -781,10 +781,10 @@ class ITUNES(DriverBase):
|
||||
zf.close()
|
||||
|
||||
break
|
||||
# else:
|
||||
# if DEBUG:
|
||||
# self.log.error(" unable to find '%s' by '%s' (%s)" %
|
||||
# (bl_book.title, bl_book.author,bl_book.uuid))
|
||||
else:
|
||||
if DEBUG:
|
||||
self.log.error(" unable to find '%s' by '%s' (%s)" %
|
||||
(bl_book.title, bl_book.author,bl_book.uuid))
|
||||
|
||||
if False:
|
||||
self._dump_booklist(booklists[0], indent = 2)
|
||||
@ -905,7 +905,8 @@ class ITUNES(DriverBase):
|
||||
|
||||
# Add new_book to self.cached_books
|
||||
if DEBUG:
|
||||
self.log.info(" adding '%s' by '%s' ['%s'] to self.cached_books" %
|
||||
self.log.info("ITUNES.upload_books()")
|
||||
self.log.info(" adding '%s' by '%s' uuid:%s to self.cached_books" %
|
||||
( metadata[i].title, metadata[i].author, metadata[i].uuid))
|
||||
self.cached_books[this_book.path] = {
|
||||
'author': metadata[i].author,
|
||||
@ -943,7 +944,11 @@ class ITUNES(DriverBase):
|
||||
new_booklist.append(this_book)
|
||||
self._update_iTunes_metadata(metadata[i], db_added, lb_added, this_book)
|
||||
|
||||
# Add new_book to self.cached_paths
|
||||
# Add new_book to self.cached_books
|
||||
if DEBUG:
|
||||
self.log.info("ITUNES.upload_books()")
|
||||
self.log.info(" adding '%s' by '%s' uuid:%s to self.cached_books" %
|
||||
( metadata[i].title, metadata[i].author, metadata[i].uuid))
|
||||
self.cached_books[this_book.path] = {
|
||||
'author': metadata[i].author[0],
|
||||
'dev_book': db_added,
|
||||
@ -1406,8 +1411,8 @@ class ITUNES(DriverBase):
|
||||
|
||||
for book in booklist:
|
||||
if isosx:
|
||||
self.log.info("%s%-40.40s %-30.30s %-10.10s" %
|
||||
(' '*indent,book.title, book.author, str(book.library_id)[-9:]))
|
||||
self.log.info("%s%-40.40s %-30.30s %-10.10s %s" %
|
||||
(' '*indent,book.title, book.author, str(book.library_id)[-9:], book.uuid))
|
||||
elif iswindows:
|
||||
self.log.info("%s%-40.40s %-30.30s" %
|
||||
(' '*indent,book.title, book.author))
|
||||
@ -1547,11 +1552,12 @@ class ITUNES(DriverBase):
|
||||
|
||||
if isosx:
|
||||
for ub in self.update_list:
|
||||
self.log.info("%s%-40.40s %-30.30s %-10.10s" %
|
||||
self.log.info("%s%-40.40s %-30.30s %-10.10s %s" %
|
||||
(' '*indent,
|
||||
ub['title'],
|
||||
ub['author'],
|
||||
str(ub['lib_book'])[-9:]))
|
||||
str(ub['lib_book'])[-9:],
|
||||
ub['uuid']))
|
||||
elif iswindows:
|
||||
for ub in self.update_list:
|
||||
self.log.info("%s%-40.40s %-30.30s" %
|
||||
@ -2342,8 +2348,10 @@ class ITUNES(DriverBase):
|
||||
if isosx:
|
||||
if DEBUG:
|
||||
self.log.info(" deleting '%s' from iDevice" % cached_book['title'])
|
||||
cached_book['dev_book'].delete()
|
||||
|
||||
try:
|
||||
cached_book['dev_book'].delete()
|
||||
except:
|
||||
self.log.error(" error deleting '%s'" % cached_book['title'])
|
||||
elif iswindows:
|
||||
hit = self._find_device_book(cached_book)
|
||||
if hit:
|
||||
@ -2802,7 +2810,7 @@ class ITUNES_ASYNC(ITUNES):
|
||||
#this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None
|
||||
this_book.library_id = library_books[book]
|
||||
this_book.size = library_books[book].size()
|
||||
this_book.uuid = library_books[book].album()
|
||||
this_book.uuid = library_books[book].composer()
|
||||
# Hack to discover if we're running in GUI environment
|
||||
if self.report_progress is not None:
|
||||
this_book.thumbnail = self._generate_thumbnail(this_book.path, library_books[book])
|
||||
@ -2842,6 +2850,7 @@ class ITUNES_ASYNC(ITUNES):
|
||||
this_book.device_collections = []
|
||||
this_book.library_id = library_books[book]
|
||||
this_book.size = library_books[book].Size
|
||||
this_book.uuid = library_books[book].Composer
|
||||
# Hack to discover if we're running in GUI environment
|
||||
if self.report_progress is not None:
|
||||
this_book.thumbnail = self._generate_thumbnail(this_book.path, library_books[book])
|
||||
|
@ -5,15 +5,16 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Timothy Legge <timlegge at gmail.com> and Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
import os, time
|
||||
import sqlite3 as sqlite
|
||||
|
||||
from calibre.devices.usbms.books import BookList
|
||||
from calibre.devices.kobo.books import Book
|
||||
from calibre.devices.kobo.books import ImageWrapper
|
||||
from calibre.devices.mime import mime_type_ext
|
||||
from calibre.devices.usbms.driver import USBMS
|
||||
from calibre.devices.usbms.driver import USBMS, debug_print
|
||||
from calibre import prints
|
||||
from calibre.devices.usbms.books import CollectionsBookList
|
||||
|
||||
class KOBO(USBMS):
|
||||
|
||||
@ -21,12 +22,15 @@ class KOBO(USBMS):
|
||||
gui_name = 'Kobo Reader'
|
||||
description = _('Communicate with the Kobo Reader')
|
||||
author = 'Timothy Legge and Kovid Goyal'
|
||||
version = (1, 0, 4)
|
||||
version = (1, 0, 6)
|
||||
|
||||
supported_platforms = ['windows', 'osx', 'linux']
|
||||
|
||||
booklist_class = CollectionsBookList
|
||||
|
||||
# Ordered list of supported formats
|
||||
FORMATS = ['epub', 'pdf']
|
||||
CAN_SET_METADATA = True
|
||||
|
||||
VENDOR_ID = [0x2237]
|
||||
PRODUCT_ID = [0x4161]
|
||||
@ -40,6 +44,12 @@ class KOBO(USBMS):
|
||||
|
||||
VIRTUAL_BOOK_EXTENSIONS = frozenset(['kobo'])
|
||||
|
||||
EXTRA_CUSTOMIZATION_MESSAGE = _('The Kobo supports only one collection '
|
||||
'currently: the \"Im_Reading\" list. Create a tag called \"Im_Reading\" ')+\
|
||||
'for automatic management'
|
||||
|
||||
EXTRA_CUSTOMIZATION_DEFAULT = ', '.join(['tags'])
|
||||
|
||||
def initialize(self):
|
||||
USBMS.initialize(self)
|
||||
self.book_class = Book
|
||||
@ -63,6 +73,8 @@ class KOBO(USBMS):
|
||||
self._card_b_prefix if oncard == 'cardb' \
|
||||
else self._main_prefix
|
||||
|
||||
self.booklist_class.rebuild_collections = self.rebuild_collections
|
||||
|
||||
# get the metadata cache
|
||||
bl = self.booklist_class(oncard, prefix, self.settings)
|
||||
need_sync = self.parse_metadata_cache(bl, prefix, self.METADATA_CACHE)
|
||||
@ -85,9 +97,7 @@ class KOBO(USBMS):
|
||||
playlist_map = {}
|
||||
|
||||
if readstatus == 1:
|
||||
if lpath not in playlist_map:
|
||||
playlist_map[lpath] = []
|
||||
playlist_map[lpath].append("I\'m Reading")
|
||||
playlist_map[lpath]= "Im_Reading"
|
||||
|
||||
path = self.normalize_path(path)
|
||||
# print "Normalized FileName: " + path
|
||||
@ -104,14 +114,17 @@ class KOBO(USBMS):
|
||||
if self.update_metadata_item(bl[idx]):
|
||||
# print 'update_metadata_item returned true'
|
||||
changed = True
|
||||
bl[idx].device_collections = playlist_map.get(lpath, [])
|
||||
if lpath in playlist_map and \
|
||||
playlist_map[lpath] not in bl[idx].device_collections:
|
||||
bl[idx].device_collections.append(playlist_map[lpath])
|
||||
else:
|
||||
if ContentType == '6':
|
||||
book = Book(prefix, lpath, title, authors, mime, date, ContentType, ImageID, size=1048576)
|
||||
else:
|
||||
book = self.book_from_path(prefix, lpath, title, authors, mime, date, ContentType, ImageID)
|
||||
# print 'Update booklist'
|
||||
book.device_collections = playlist_map.get(book.lpath, [])
|
||||
book.device_collections = [playlist_map[lpath]] if lpath in playlist_map else []
|
||||
|
||||
if bl.add_book(book, replace_metadata=False):
|
||||
changed = True
|
||||
except: # Probably a path encoding error
|
||||
@ -398,3 +411,95 @@ class KOBO(USBMS):
|
||||
size = os.stat(cls.normalize_path(os.path.join(prefix, lpath))).st_size
|
||||
book = Book(prefix, lpath, title, authors, mime, date, ContentType, ImageID, size=size, other=mi)
|
||||
return book
|
||||
|
||||
def get_device_paths(self):
|
||||
paths, prefixes = {}, {}
|
||||
for prefix, path, source_id in [
|
||||
('main', 'metadata.calibre', 0),
|
||||
('card_a', 'metadata.calibre', 1),
|
||||
('card_b', 'metadata.calibre', 2)
|
||||
]:
|
||||
prefix = getattr(self, '_%s_prefix'%prefix)
|
||||
if prefix is not None and os.path.exists(prefix):
|
||||
paths[source_id] = os.path.join(prefix, *(path.split('/')))
|
||||
return paths
|
||||
|
||||
def update_device_database_collections(self, booklists, collections_attributes):
|
||||
# debug_print('Starting update_device_database_collections', collections_attributes)
|
||||
|
||||
# Force collections_attributes to be 'tags' as no other is currently supported
|
||||
# debug_print('KOBO: overriding the provided collections_attributes:', collections_attributes)
|
||||
collections_attributes = ['tags']
|
||||
|
||||
collections = booklists.get_collections(collections_attributes)
|
||||
# debug_print('Collections', collections)
|
||||
for category, books in collections.items():
|
||||
if category == 'Im_Reading':
|
||||
# Create a connection to the sqlite database
|
||||
connection = sqlite.connect(self._main_prefix + '.kobo/KoboReader.sqlite')
|
||||
cursor = connection.cursor()
|
||||
|
||||
# Reset Im_Reading list in the database
|
||||
query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null'
|
||||
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 = ['Im_Reading']
|
||||
|
||||
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 = (datelastread,ContentID,)
|
||||
|
||||
try:
|
||||
cursor.execute('update content set ReadStatus=1,FirstTimeReading=\'false\',DateLastRead=? where BookID is Null and ContentID = ?', t)
|
||||
except:
|
||||
debug_print('Database Exception: Unable create Im_Reading list')
|
||||
raise
|
||||
else:
|
||||
connection.commit()
|
||||
# debug_print('Database: Commit create Im_Reading list')
|
||||
|
||||
cursor.close()
|
||||
connection.close()
|
||||
|
||||
# debug_print('Finished update_device_database_collections', collections_attributes)
|
||||
|
||||
def sync_booklists(self, booklists, end_session=True):
|
||||
# debug_print('KOBO: started sync_booklists')
|
||||
paths = self.get_device_paths()
|
||||
|
||||
blists = {}
|
||||
for i in paths:
|
||||
if booklists[i] is not None:
|
||||
#debug_print('Booklist: ', i)
|
||||
blists[i] = booklists[i]
|
||||
opts = self.settings()
|
||||
if opts.extra_customization:
|
||||
collections = [x.lower().strip() for x in
|
||||
opts.extra_customization.split(',')]
|
||||
else:
|
||||
collections = []
|
||||
|
||||
#debug_print('KOBO: collection fields:', collections)
|
||||
for i, blist in blists.items():
|
||||
self.update_device_database_collections(blist, collections)
|
||||
|
||||
USBMS.sync_booklists(self, booklists, end_session=end_session)
|
||||
#debug_print('KOBO: finished sync_booklists')
|
||||
|
||||
def rebuild_collections(self, booklist, oncard):
|
||||
collections_attributes = []
|
||||
self.update_device_database_collections(booklist, collections_attributes)
|
||||
|
||||
|
@ -35,16 +35,16 @@ class PRS505(USBMS):
|
||||
|
||||
VENDOR_NAME = 'SONY'
|
||||
WINDOWS_MAIN_MEM = re.compile(
|
||||
r'(PRS-(505|300|500))|'
|
||||
r'(PRS-((700[#/])|((6|9)00&)))'
|
||||
r'(PRS-(505|500|300))|'
|
||||
r'(PRS-((700[#/])|((6|9|3)(0|5)0&)))'
|
||||
)
|
||||
WINDOWS_CARD_A_MEM = re.compile(
|
||||
r'(PRS-(505|500)[#/]\S+:MS)|'
|
||||
r'(PRS-((700[/#]\S+:)|((6|9)00[#_]))MS)'
|
||||
r'(PRS-((700[/#]\S+:)|((6|9)(0|5)0[#_]))MS)'
|
||||
)
|
||||
WINDOWS_CARD_B_MEM = re.compile(
|
||||
r'(PRS-(505|500)[#/]\S+:SD)|'
|
||||
r'(PRS-((700[/#]\S+:)|((6|9)00[#_]))SD)'
|
||||
r'(PRS-((700[/#]\S+:)|((6|9)(0|5)0[#_]))SD)'
|
||||
)
|
||||
|
||||
|
||||
|
@ -366,7 +366,7 @@ OptionRecommendation(name='html_unwrap_factor',
|
||||
recommended_value=0.40, level=OptionRecommendation.LOW,
|
||||
help=_('Scale used to determine the length at which a line should '
|
||||
'be unwrapped if preprocess is enabled. Valid values are a decimal between 0 and 1. The '
|
||||
'default is 0.40, just below the median line length. This will unwrap typical books '
|
||||
'default is 0.40, just below the median line length. This will unwrap typical books '
|
||||
' with hard line breaks, but should be reduced if the line length is variable.'
|
||||
)
|
||||
),
|
||||
|
@ -92,7 +92,7 @@ class PreProcessor(object):
|
||||
# If more than 40% of the lines are empty paragraphs then delete them to clean up spacing
|
||||
linereg = re.compile('(?<=<p).*?(?=</p>)', re.IGNORECASE|re.DOTALL)
|
||||
blankreg = re.compile(r'\s*<p[^>]*>\s*(<(b|i|u)>)?\s*(</(b|i|u)>)?\s*</p>', re.IGNORECASE)
|
||||
multi_blank = re.compile(r'(\s*<p[^>]*>\s*(<(b|i|u)>)?\s*(</(b|i|u)>)?\s*</p>){2,}', re.IGNORECASE)
|
||||
#multi_blank = re.compile(r'(\s*<p[^>]*>\s*(<(b|i|u)>)?\s*(</(b|i|u)>)?\s*</p>){2,}', re.IGNORECASE)
|
||||
blanklines = blankreg.findall(html)
|
||||
lines = linereg.findall(html)
|
||||
if len(lines) > 1:
|
||||
@ -149,11 +149,8 @@ class PreProcessor(object):
|
||||
format = 'html'
|
||||
|
||||
# Calculate Length
|
||||
#if getattr(self.extra_opts, 'html_unwrap_factor', 0.0) > 0.01:
|
||||
length = line_length('pdf', html, getattr(self.extra_opts, 'html_unwrap_factor'))
|
||||
#else:
|
||||
# length = line_length(format, html, 0.4)
|
||||
# self.log("#@#%!$@#$ - didn't find unwrap_factor")
|
||||
length = line_length('pdf', html, getattr(self.extra_opts,
|
||||
'html_unwrap_factor', 0.4))
|
||||
self.log("*** Median line length is " + str(length) + ",calculated with " + format + " format ***")
|
||||
#
|
||||
# Unwrap and/or delete soft-hyphens, hyphens
|
||||
|
@ -28,6 +28,9 @@ class FB2Output(OutputFormatPlugin):
|
||||
])
|
||||
|
||||
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||
from calibre.ebooks.oeb.transforms.jacket import linearize_jacket
|
||||
linearize_jacket(oeb_book)
|
||||
|
||||
fb2mlizer = FB2MLizer(log)
|
||||
fb2_content = fb2mlizer.extract_content(oeb_book, opts)
|
||||
|
||||
|
@ -420,7 +420,7 @@ class LRFInput(InputFormatPlugin):
|
||||
styles.write()
|
||||
return os.path.abspath('content.opf')
|
||||
|
||||
def preprocess_html(self, html):
|
||||
def preprocess_html(self, html):
|
||||
preprocessor = PreProcessor(log=getattr(self, 'log', None))
|
||||
return preprocessor(html)
|
||||
|
||||
|
@ -99,7 +99,8 @@ class CoverManager(object):
|
||||
series_string = None
|
||||
if m.series and m.series_index:
|
||||
series_string = _('Book %s of %s')%(
|
||||
fmt_sidx(m.series_index[0], use_roman=True), m.series[0])
|
||||
fmt_sidx(m.series_index[0], use_roman=True),
|
||||
unicode(m.series[0]))
|
||||
|
||||
try:
|
||||
from calibre.ebooks import calibre_cover
|
||||
|
@ -147,7 +147,6 @@ class CSSFlattener(object):
|
||||
extra_css=css)
|
||||
self.stylizers[item] = stylizer
|
||||
|
||||
|
||||
def baseline_node(self, node, stylizer, sizes, csize):
|
||||
csize = stylizer.style(node)['font-size']
|
||||
if node.text:
|
||||
@ -195,7 +194,7 @@ class CSSFlattener(object):
|
||||
value = 0.0
|
||||
cssdict[property] = "%0.5fem" % (value / fsize)
|
||||
|
||||
def flatten_node(self, node, stylizer, names, styles, psize, left=0):
|
||||
def flatten_node(self, node, stylizer, names, styles, psize, item_id, left=0):
|
||||
if not isinstance(node.tag, basestring) \
|
||||
or namespace(node.tag) != XHTML_NS:
|
||||
return
|
||||
@ -287,15 +286,18 @@ class CSSFlattener(object):
|
||||
if self.lineh and 'line-height' not in cssdict:
|
||||
lineh = self.lineh / psize
|
||||
cssdict['line-height'] = "%0.5fem" % lineh
|
||||
|
||||
if (self.context.remove_paragraph_spacing or
|
||||
self.context.insert_blank_line) and tag in ('p', 'div'):
|
||||
for prop in ('margin', 'padding', 'border'):
|
||||
for edge in ('top', 'bottom'):
|
||||
cssdict['%s-%s'%(prop, edge)] = '0pt'
|
||||
if item_id != 'calibre_jacket' or self.context.output_profile.name == 'Kindle':
|
||||
for prop in ('margin', 'padding', 'border'):
|
||||
for edge in ('top', 'bottom'):
|
||||
cssdict['%s-%s'%(prop, edge)] = '0pt'
|
||||
if self.context.insert_blank_line:
|
||||
cssdict['margin-top'] = cssdict['margin-bottom'] = '0.5em'
|
||||
if self.context.remove_paragraph_spacing:
|
||||
cssdict['text-indent'] = "%1.1fem" % self.context.remove_paragraph_spacing_indent_size
|
||||
|
||||
if cssdict:
|
||||
items = cssdict.items()
|
||||
items.sort()
|
||||
@ -314,7 +316,7 @@ class CSSFlattener(object):
|
||||
if 'style' in node.attrib:
|
||||
del node.attrib['style']
|
||||
for child in node:
|
||||
self.flatten_node(child, stylizer, names, styles, psize, left)
|
||||
self.flatten_node(child, stylizer, names, styles, psize, item_id, left)
|
||||
|
||||
def flatten_head(self, item, stylizer, href):
|
||||
html = item.data
|
||||
@ -361,7 +363,7 @@ class CSSFlattener(object):
|
||||
stylizer = self.stylizers[item]
|
||||
body = html.find(XHTML('body'))
|
||||
fsize = self.context.dest.fbase
|
||||
self.flatten_node(body, stylizer, names, styles, fsize)
|
||||
self.flatten_node(body, stylizer, names, styles, fsize, item.id)
|
||||
items = [(key, val) for (val, key) in styles.items()]
|
||||
items.sort()
|
||||
css = ''.join(".%s {\n%s;\n}\n\n" % (key, val) for key, val in items)
|
||||
|
@ -6,139 +6,200 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import textwrap
|
||||
import sys
|
||||
from xml.sax.saxutils import escape
|
||||
from itertools import repeat
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from calibre.ebooks.oeb.base import XPath, XPNSMAP
|
||||
from calibre import guess_type
|
||||
from calibre import guess_type, strftime
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||
from calibre.ebooks.oeb.base import XPath, XHTML_NS, XHTML
|
||||
from calibre.library.comments import comments_to_html
|
||||
|
||||
JACKET_XPATH = '//h:meta[@name="calibre-content" and @content="jacket"]'
|
||||
|
||||
class Jacket(object):
|
||||
'''
|
||||
Book jacket manipulation. Remove first image and insert comments at start of
|
||||
book.
|
||||
'''
|
||||
|
||||
JACKET_TEMPLATE = textwrap.dedent(u'''\
|
||||
<html xmlns="%(xmlns)s">
|
||||
<head>
|
||||
<title>%(title)s</title>
|
||||
<meta name="calibre-content" content="jacket"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="calibre_rescale_100">
|
||||
<div style="text-align:center">
|
||||
<h1 class="calibre_rescale_180">%(title)s</h1>
|
||||
<h2 class="calibre_rescale_140">%(jacket)s</h2>
|
||||
<div class="calibre_rescale_100">%(series)s</div>
|
||||
<div class="calibre_rescale_100">%(rating)s</div>
|
||||
<div class="calibre_rescale_100">%(tags)s</div>
|
||||
</div>
|
||||
<div style="margin-top:2em" class="calibre_rescale_100">
|
||||
%(comments)s
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
''')
|
||||
def remove_images(self, item, limit=1):
|
||||
path = XPath('//h:img[@src]')
|
||||
removed = 0
|
||||
for img in path(item.data):
|
||||
if removed >= limit:
|
||||
break
|
||||
href = item.abshref(img.get('src'))
|
||||
image = self.oeb.manifest.hrefs.get(href, None)
|
||||
if image is not None:
|
||||
self.oeb.manifest.remove(image)
|
||||
img.getparent().remove(img)
|
||||
removed += 1
|
||||
return removed
|
||||
|
||||
def remove_first_image(self):
|
||||
path = XPath('//h:img[@src]')
|
||||
for i, item in enumerate(self.oeb.spine):
|
||||
if i > 2: break
|
||||
for img in path(item.data):
|
||||
href = item.abshref(img.get('src'))
|
||||
image = self.oeb.manifest.hrefs.get(href, None)
|
||||
if image is not None:
|
||||
self.log('Removing first image', img.get('src'))
|
||||
self.oeb.manifest.remove(image)
|
||||
img.getparent().remove(img)
|
||||
return
|
||||
|
||||
def get_rating(self, rating):
|
||||
ans = ''
|
||||
if rating is None:
|
||||
return
|
||||
try:
|
||||
num = float(rating)/2
|
||||
except:
|
||||
return ans
|
||||
num = max(0, num)
|
||||
num = min(num, 5)
|
||||
if num < 1:
|
||||
return ans
|
||||
id, href = self.oeb.manifest.generate('star', 'star.png')
|
||||
self.oeb.manifest.add(id, href, 'image/png', data=I('star.png', data=True))
|
||||
ans = 'Rating: ' + ''.join(repeat('<img style="vertical-align:text-top" alt="star" src="%s" />'%href, num))
|
||||
return ans
|
||||
for item in self.oeb.spine:
|
||||
removed = self.remove_images(item)
|
||||
if removed > 0:
|
||||
self.log('Removed first image')
|
||||
break
|
||||
|
||||
def insert_metadata(self, mi):
|
||||
self.log('Inserting metadata into book...')
|
||||
comments = mi.comments
|
||||
if not comments:
|
||||
try:
|
||||
comments = unicode(self.oeb.metadata.description[0])
|
||||
except:
|
||||
comments = ''
|
||||
if not comments.strip():
|
||||
comments = ''
|
||||
orig_comments = comments
|
||||
if comments:
|
||||
comments = comments_to_html(comments)
|
||||
series = '<b>Series: </b>' + escape(mi.series if mi.series else '')
|
||||
if mi.series and mi.series_index is not None:
|
||||
series += escape(' [%s]'%mi.format_series_index())
|
||||
if not mi.series:
|
||||
series = ''
|
||||
tags = mi.tags
|
||||
if not tags:
|
||||
try:
|
||||
tags = map(unicode, self.oeb.metadata.subject)
|
||||
except:
|
||||
tags = []
|
||||
if tags:
|
||||
tags = '<b>Tags: </b>' + self.opts.dest.tags_to_string(tags)
|
||||
else:
|
||||
tags = ''
|
||||
|
||||
try:
|
||||
title = mi.title if mi.title else unicode(self.oeb.metadata.title[0])
|
||||
tags = map(unicode, self.oeb.metadata.subject)
|
||||
except:
|
||||
tags = []
|
||||
|
||||
try:
|
||||
comments = unicode(self.oeb.metadata.description[0])
|
||||
except:
|
||||
comments = ''
|
||||
|
||||
try:
|
||||
title = unicode(self.oeb.metadata.title[0])
|
||||
except:
|
||||
title = _('Unknown')
|
||||
|
||||
def generate_html(comments):
|
||||
return self.JACKET_TEMPLATE%dict(xmlns=XPNSMAP['h'],
|
||||
title=escape(title), comments=comments,
|
||||
jacket=escape(_('Book Jacket')), series=series,
|
||||
tags=tags, rating=self.get_rating(mi.rating))
|
||||
id, href = self.oeb.manifest.generate('jacket', 'jacket.xhtml')
|
||||
from calibre.ebooks.oeb.base import RECOVER_PARSER, XPath
|
||||
try:
|
||||
root = etree.fromstring(generate_html(comments), parser=RECOVER_PARSER)
|
||||
except:
|
||||
root = etree.fromstring(generate_html(escape(orig_comments)),
|
||||
parser=RECOVER_PARSER)
|
||||
jacket = XPath('//h:meta[@name="calibre-content" and @content="jacket"]')
|
||||
found = None
|
||||
for item in list(self.oeb.spine)[:4]:
|
||||
try:
|
||||
if jacket(item.data):
|
||||
found = item
|
||||
break
|
||||
except:
|
||||
continue
|
||||
if found is None:
|
||||
item = self.oeb.manifest.add(id, href, guess_type(href)[0], data=root)
|
||||
self.oeb.spine.insert(0, item, True)
|
||||
else:
|
||||
self.log('Found existing book jacket, replacing...')
|
||||
found.data = root
|
||||
root = render_jacket(mi, self.opts.output_profile,
|
||||
alt_title=title, alt_tags=tags,
|
||||
alt_comments=comments)
|
||||
id, href = self.oeb.manifest.generate('calibre_jacket', 'jacket.xhtml')
|
||||
|
||||
item = self.oeb.manifest.add(id, href, guess_type(href)[0], data=root)
|
||||
self.oeb.spine.insert(0, item, True)
|
||||
|
||||
def remove_existing_jacket(self):
|
||||
for x in self.oeb.spine[:4]:
|
||||
if XPath(JACKET_XPATH)(x.data):
|
||||
self.remove_images(x, limit=sys.maxint)
|
||||
self.oeb.manifest.remove(x)
|
||||
self.log('Removed existing jacket')
|
||||
break
|
||||
|
||||
def __call__(self, oeb, opts, metadata):
|
||||
'''
|
||||
Add metadata in jacket.xhtml if specified in opts
|
||||
If not specified, remove previous jacket instance
|
||||
'''
|
||||
self.oeb, self.opts, self.log = oeb, opts, oeb.log
|
||||
self.remove_existing_jacket()
|
||||
if opts.remove_first_image:
|
||||
self.remove_first_image()
|
||||
if opts.insert_metadata:
|
||||
self.insert_metadata(metadata)
|
||||
|
||||
# Render Jacket {{{
|
||||
|
||||
def get_rating(rating, rchar):
|
||||
ans = ''
|
||||
try:
|
||||
num = float(rating)/2
|
||||
except:
|
||||
return ans
|
||||
num = max(0, num)
|
||||
num = min(num, 5)
|
||||
if num < 1:
|
||||
return ans
|
||||
|
||||
ans = rchar * int(num)
|
||||
return ans
|
||||
|
||||
|
||||
def render_jacket(mi, output_profile,
|
||||
alt_title=_('Unknown'), alt_tags=[], alt_comments=''):
|
||||
css = P('jacket/stylesheet.css', data=True).decode('utf-8')
|
||||
|
||||
try:
|
||||
title_str = mi.title if mi.title else alt_title
|
||||
except:
|
||||
title_str = _('Unknown')
|
||||
title = '<span class="title">%s</span>' % (escape(title_str))
|
||||
|
||||
series = escape(mi.series if mi.series else '')
|
||||
if mi.series and mi.series_index is not None:
|
||||
series += escape(' [%s]'%mi.format_series_index())
|
||||
if not mi.series:
|
||||
series = ''
|
||||
|
||||
try:
|
||||
pubdate = strftime(u'%Y', mi.pubdate.timetuple())
|
||||
except:
|
||||
pubdate = ''
|
||||
|
||||
rating = get_rating(mi.rating, output_profile.ratings_char)
|
||||
|
||||
tags = mi.tags if mi.tags else alt_tags
|
||||
if tags:
|
||||
tags = output_profile.tags_to_string(tags)
|
||||
else:
|
||||
tags = ''
|
||||
|
||||
comments = mi.comments if mi.comments else alt_comments
|
||||
comments = comments.strip()
|
||||
orig_comments = comments
|
||||
if comments:
|
||||
comments = comments_to_html(comments)
|
||||
|
||||
def generate_html(comments):
|
||||
args = dict(xmlns=XHTML_NS,
|
||||
title_str=title_str,
|
||||
css=css,
|
||||
title=title,
|
||||
pubdate_label=_('Published'), pubdate=pubdate,
|
||||
series_label=_('Series'), series=series,
|
||||
rating_label=_('Rating'), rating=rating,
|
||||
tags_label=_('Tags'), tags=tags,
|
||||
comments=comments,
|
||||
footer=''
|
||||
)
|
||||
|
||||
generated_html = P('jacket/template.xhtml',
|
||||
data=True).decode('utf-8').format(**args)
|
||||
|
||||
# Post-process the generated html to strip out empty header items
|
||||
soup = BeautifulSoup(generated_html)
|
||||
if not series:
|
||||
series_tag = soup.find('tr', attrs={'class':'cbj_series'})
|
||||
series_tag.extract()
|
||||
if not rating:
|
||||
rating_tag = soup.find('tr', attrs={'class':'cbj_rating'})
|
||||
rating_tag.extract()
|
||||
if not tags:
|
||||
tags_tag = soup.find('tr', attrs={'class':'cbj_tags'})
|
||||
tags_tag.extract()
|
||||
if not pubdate:
|
||||
pubdate_tag = soup.find('tr', attrs={'class':'cbj_pubdate'})
|
||||
pubdate_tag.extract()
|
||||
if output_profile.short_name != 'kindle':
|
||||
hr_tag = soup.find('hr', attrs={'class':'cbj_kindle_banner_hr'})
|
||||
hr_tag.extract()
|
||||
|
||||
return soup.renderContents(None)
|
||||
|
||||
from calibre.ebooks.oeb.base import RECOVER_PARSER
|
||||
|
||||
try:
|
||||
root = etree.fromstring(generate_html(comments), parser=RECOVER_PARSER)
|
||||
except:
|
||||
try:
|
||||
root = etree.fromstring(generate_html(escape(orig_comments)),
|
||||
parser=RECOVER_PARSER)
|
||||
except:
|
||||
root = etree.fromstring(generate_html(''),
|
||||
parser=RECOVER_PARSER)
|
||||
return root
|
||||
|
||||
# }}}
|
||||
|
||||
def linearize_jacket(oeb):
|
||||
for x in oeb.spine[:4]:
|
||||
if XPath(JACKET_XPATH)(x.data):
|
||||
for e in XPath('//h:table|//h:tr|//h:th')(x.data):
|
||||
e.tag = XHTML('div')
|
||||
for e in XPath('//h:td')(x.data):
|
||||
e.tag = XHTML('span')
|
||||
break
|
||||
|
||||
|
@ -72,10 +72,13 @@ class RescaleImages(object):
|
||||
Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
|
||||
data = pixmap_to_data(img, format=ext)
|
||||
else:
|
||||
im = im.resize((int(new_width), int(new_height)), PILImage.ANTIALIAS)
|
||||
of = cStringIO.StringIO()
|
||||
im.convert('RGB').save(of, ext)
|
||||
data = of.getvalue()
|
||||
try:
|
||||
im = im.resize((int(new_width), int(new_height)), PILImage.ANTIALIAS)
|
||||
of = cStringIO.StringIO()
|
||||
im.convert('RGB').save(of, ext)
|
||||
data = of.getvalue()
|
||||
except:
|
||||
self.log.exception('Failed to rescale image')
|
||||
if data is not None:
|
||||
item.data = data
|
||||
item.unload_data_from_memory()
|
||||
|
@ -50,6 +50,7 @@ gprefs.defaults['action-layout-context-menu-device'] = (
|
||||
gprefs.defaults['show_splash_screen'] = True
|
||||
gprefs.defaults['toolbar_icon_size'] = 'medium'
|
||||
gprefs.defaults['toolbar_text'] = 'auto'
|
||||
gprefs.defaults['show_child_bar'] = False
|
||||
|
||||
# }}}
|
||||
|
||||
|
@ -71,6 +71,12 @@ class InterfaceAction(QObject):
|
||||
all_locations = frozenset(['toolbar', 'toolbar-device', 'context-menu',
|
||||
'context-menu-device'])
|
||||
|
||||
#: Type of action
|
||||
#: 'current' means acts on the current view
|
||||
#: 'global' means an action that does not act on the current view, but rather
|
||||
#: on calibre as a whole
|
||||
action_type = 'global'
|
||||
|
||||
def __init__(self, parent, site_customization):
|
||||
QObject.__init__(self, parent)
|
||||
self.setObjectName(self.name)
|
||||
|
@ -25,6 +25,7 @@ class AddAction(InterfaceAction):
|
||||
action_spec = (_('Add books'), 'add_book.png',
|
||||
_('Add books to the calibre library/device from files on your computer')
|
||||
, _('A'))
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
self._add_filesystem_book = self.Dispatcher(self.__add_filesystem_book)
|
||||
|
@ -13,6 +13,7 @@ class AddToLibraryAction(InterfaceAction):
|
||||
action_spec = (_('Add books to library'), 'add_book.png',
|
||||
_('Add books to your calibre library from the connected device'), None)
|
||||
dont_add_to = frozenset(['toolbar', 'context-menu'])
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
self.qaction.triggered.connect(self.add_books_to_library)
|
||||
|
@ -18,6 +18,7 @@ class FetchAnnotationsAction(InterfaceAction):
|
||||
|
||||
name = 'Fetch Annotations'
|
||||
action_spec = (_('Fetch annotations (experimental)'), None, None, None)
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
pass
|
||||
|
@ -21,6 +21,7 @@ class ConvertAction(InterfaceAction):
|
||||
name = 'Convert Books'
|
||||
action_spec = (_('Convert books'), 'convert.png', None, _('C'))
|
||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
cm = QMenu()
|
||||
|
@ -80,6 +80,7 @@ class CopyToLibraryAction(InterfaceAction):
|
||||
_('Copy selected books to the specified library'), None)
|
||||
popup_type = QToolButton.InstantPopup
|
||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
self.menu = QMenu(self.gui)
|
||||
|
@ -16,6 +16,7 @@ class DeleteAction(InterfaceAction):
|
||||
|
||||
name = 'Remove Books'
|
||||
action_spec = (_('Remove books'), 'trash.png', None, _('Del'))
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
self.qaction.triggered.connect(self.delete_books)
|
||||
|
@ -13,6 +13,7 @@ class EditCollectionsAction(InterfaceAction):
|
||||
action_spec = (_('Manage collections'), None,
|
||||
_('Manage the collections on this device'), None)
|
||||
dont_add_to = frozenset(['toolbar', 'context-menu'])
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
self.qaction.triggered.connect(self.edit_collections)
|
||||
|
@ -22,6 +22,7 @@ class EditMetadataAction(InterfaceAction):
|
||||
|
||||
name = 'Edit Metadata'
|
||||
action_spec = (_('Edit metadata'), 'edit_input.png', None, _('E'))
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
self.create_action(spec=(_('Merge book records'), 'merge_books.png',
|
||||
|
@ -14,6 +14,7 @@ class OpenFolderAction(InterfaceAction):
|
||||
action_spec = (_('Open containing folder'), 'document_open.png', None,
|
||||
_('O'))
|
||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
self.qaction.triggered.connect(self.gui.iactions['View'].view_folder)
|
||||
|
@ -38,6 +38,7 @@ class SaveToDiskAction(InterfaceAction):
|
||||
|
||||
name = "Save To Disk"
|
||||
action_spec = (_('Save to disk'), 'save.png', None, _('S'))
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
self.qaction.triggered.connect(self.save_to_disk)
|
||||
|
@ -16,6 +16,7 @@ class ShowBookDetailsAction(InterfaceAction):
|
||||
action_spec = (_('Show book details'), 'dialog_information.png', None,
|
||||
_('I'))
|
||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
self.qaction.triggered.connect(self.show_book_info)
|
||||
|
@ -16,6 +16,7 @@ class SimilarBooksAction(InterfaceAction):
|
||||
name = 'Similar Books'
|
||||
action_spec = (_('Similar books...'), None, None, None)
|
||||
popup_type = QToolButton.InstantPopup
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
m = QMenu(self.gui)
|
||||
|
@ -22,6 +22,7 @@ class ViewAction(InterfaceAction):
|
||||
|
||||
name = 'View'
|
||||
action_spec = (_('View'), 'view.png', None, _('V'))
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
self.persistent_files = []
|
||||
|
@ -28,6 +28,8 @@ class StructureDetectionWidget(Widget, Ui_Form):
|
||||
'preprocess_html', 'remove_header', 'header_regex',
|
||||
'remove_footer', 'footer_regex','html_unwrap_factor']
|
||||
)
|
||||
self.opt_html_unwrap_factor.setEnabled(False)
|
||||
self.huf_label.setEnabled(False)
|
||||
self.db, self.book_id = db, book_id
|
||||
for x in ('pagebreak', 'rule', 'both', 'none'):
|
||||
self.opt_chapter_mark.addItem(x)
|
||||
@ -66,6 +68,6 @@ class StructureDetectionWidget(Widget, Ui_Form):
|
||||
return True
|
||||
|
||||
def set_value_handler(self, g, val):
|
||||
if val is None and isinstance(g, QDoubleSpinBox):
|
||||
if val is None and g is self.opt_html_unwrap_factor:
|
||||
g.setValue(0.0)
|
||||
return True
|
@ -14,10 +14,10 @@
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="XPathEdit" name="opt_chapter" native="true"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Chapter &mark:</string>
|
||||
@ -27,28 +27,31 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="1" column="2">
|
||||
<widget class="QComboBox" name="opt_chapter_mark">
|
||||
<property name="minimumContentsLength">
|
||||
<number>20</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_remove_first_image">
|
||||
<property name="text">
|
||||
<string>Remove first &image</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_insert_metadata">
|
||||
<property name="text">
|
||||
<string>Insert &metadata as page at start of book</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="0" colspan="2">
|
||||
<item row="11" column="0" colspan="3">
|
||||
<widget class="XPathEdit" name="opt_page_breaks_before" native="true"/>
|
||||
</item>
|
||||
<item row="12" column="0" colspan="3">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@ -61,52 +64,41 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<item row="8" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_remove_footer">
|
||||
<property name="text">
|
||||
<string>Remove F&ooter</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_remove_header">
|
||||
<property name="text">
|
||||
<string>Remove H&eader</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<item row="7" column="0" colspan="3">
|
||||
<widget class="RegexEdit" name="opt_header_regex" native="true"/>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<widget class="RegexEdit" name="opt_footer_regex" native="true">
|
||||
<zorder>opt_page_breaks_before</zorder>
|
||||
</widget>
|
||||
<item row="9" column="0" colspan="3">
|
||||
<widget class="RegexEdit" name="opt_footer_regex" native="true"/>
|
||||
</item>
|
||||
<item row="10" column="0" colspan="2">
|
||||
<widget class="XPathEdit" name="opt_page_breaks_before" native="true">
|
||||
<zorder>opt_footer_regex</zorder>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QCheckBox" name="opt_preprocess_html">
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="huf_label">
|
||||
<property name="text">
|
||||
<string>&Preprocess input file to possibly improve structure detection</string>
|
||||
<string>Line &un-wrap factor during preprocess:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_html_unwrap_factor</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Line Un-Wrapping Factor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<item row="4" column="2">
|
||||
<widget class="QDoubleSpinBox" name="opt_html_unwrap_factor">
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
@ -118,6 +110,26 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_preprocess_html">
|
||||
<property name="text">
|
||||
<string>&Preprocess input file to possibly improve structure detection</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
@ -135,5 +147,38 @@
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>opt_preprocess_html</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>opt_html_unwrap_factor</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>328</x>
|
||||
<y>87</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>481</x>
|
||||
<y>113</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>opt_preprocess_html</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>huf_label</receiver>
|
||||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>295</x>
|
||||
<y>88</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>291</x>
|
||||
<y>105</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
|
@ -627,12 +627,11 @@ class DeviceMixin(object): # {{{
|
||||
def connect_to_folder(self):
|
||||
dir = choose_dir(self, 'Select Device Folder',
|
||||
_('Select folder to open as device'))
|
||||
kls = FOLDER_DEVICE
|
||||
self.device_manager.mount_device(kls=kls, kind='folder', path=dir)
|
||||
if dir is not None:
|
||||
self.device_manager.mount_device(kls=FOLDER_DEVICE, kind='folder', path=dir)
|
||||
|
||||
def connect_to_itunes(self):
|
||||
kls = ITUNES_ASYNC
|
||||
self.device_manager.mount_device(kls=kls, kind='itunes', path=None)
|
||||
self.device_manager.mount_device(kls=ITUNES_ASYNC, kind='itunes', path=None)
|
||||
|
||||
# disconnect from both folder and itunes devices
|
||||
def disconnect_mounted_device(self):
|
||||
@ -746,6 +745,7 @@ class DeviceMixin(object): # {{{
|
||||
if job.failed:
|
||||
self.device_job_exception(job)
|
||||
return
|
||||
# set_books_in_library might schedule a sync_booklists job
|
||||
self.set_books_in_library(job.result, reset=True)
|
||||
mainlist, cardalist, cardblist = job.result
|
||||
self.memory_view.set_database(mainlist)
|
||||
@ -790,11 +790,12 @@ class DeviceMixin(object): # {{{
|
||||
self.device_manager.remove_books_from_metadata(paths,
|
||||
self.booklists())
|
||||
model.paths_deleted(paths)
|
||||
self.upload_booklists()
|
||||
# Force recomputation the library's ondevice info. We need to call
|
||||
# set_books_in_library even though books were not added because
|
||||
# the deleted book might have been an exact match.
|
||||
self.set_books_in_library(self.booklists(), reset=True)
|
||||
# the deleted book might have been an exact match. Upload the booklists
|
||||
# if set_books_in_library did not.
|
||||
if not self.set_books_in_library(self.booklists(), reset=True):
|
||||
self.upload_booklists()
|
||||
self.book_on_device(None, None, reset=True)
|
||||
# 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.
|
||||
@ -1231,7 +1232,7 @@ class DeviceMixin(object): # {{{
|
||||
self.location_manager.update_devices(cp, fs,
|
||||
self.device_manager.device.icon)
|
||||
# reset the views so that up-to-date info is shown. These need to be
|
||||
# here because the sony driver updates collections in sync_booklists
|
||||
# here because some drivers update collections in sync_booklists
|
||||
self.memory_view.reset()
|
||||
self.card_a_view.reset()
|
||||
self.card_b_view.reset()
|
||||
@ -1281,8 +1282,6 @@ class DeviceMixin(object): # {{{
|
||||
self.device_manager.add_books_to_metadata(job.result,
|
||||
metadata, self.booklists())
|
||||
|
||||
self.upload_booklists()
|
||||
|
||||
books_to_be_deleted = []
|
||||
if memory and memory[1]:
|
||||
books_to_be_deleted = memory[1]
|
||||
@ -1292,12 +1291,15 @@ class DeviceMixin(object): # {{{
|
||||
# book already there with a different book. This happens frequently in
|
||||
# news. When this happens, the book match indication will be wrong
|
||||
# because the UUID changed. Force both the device and the library view
|
||||
# to refresh the flags.
|
||||
self.set_books_in_library(self.booklists(), reset=True)
|
||||
# to refresh the flags. Set_books_in_library could upload the booklists.
|
||||
# If it does not, then do it here.
|
||||
if not self.set_books_in_library(self.booklists(), reset=True):
|
||||
self.upload_booklists()
|
||||
self.book_on_device(None, reset=True)
|
||||
self.refresh_ondevice_info(device_connected = True)
|
||||
|
||||
view = self.card_a_view if on_card == 'carda' else self.card_b_view if on_card == 'cardb' else self.memory_view
|
||||
view = self.card_a_view if on_card == 'carda' else \
|
||||
self.card_b_view if on_card == 'cardb' else self.memory_view
|
||||
view.model().resort(reset=False)
|
||||
view.model().research()
|
||||
for f in files:
|
||||
@ -1372,7 +1374,7 @@ class DeviceMixin(object): # {{{
|
||||
try:
|
||||
db = self.library_view.model().db
|
||||
except:
|
||||
return
|
||||
return False
|
||||
# Build a cache (map) of the library, so the search isn't On**2
|
||||
self.db_book_title_cache = {}
|
||||
self.db_book_uuid_cache = {}
|
||||
@ -1467,10 +1469,13 @@ class DeviceMixin(object): # {{{
|
||||
# Set author_sort if it isn't already
|
||||
asort = getattr(book, 'author_sort', None)
|
||||
if not asort and book.authors:
|
||||
book.author_sort = self.library_view.model().db.author_sort_from_authors(book.authors)
|
||||
book.author_sort = self.library_view.model().db.\
|
||||
author_sort_from_authors(book.authors)
|
||||
|
||||
if update_metadata:
|
||||
if self.device_manager.is_device_connected:
|
||||
self.device_manager.sync_booklists(None, booklists)
|
||||
self.device_manager.sync_booklists(
|
||||
Dispatcher(self.metadata_synced), booklists)
|
||||
return update_metadata
|
||||
# }}}
|
||||
|
||||
|
@ -6,10 +6,7 @@ The dialog used to edit meta information for a book as well as
|
||||
add/remove formats
|
||||
'''
|
||||
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
import os, re, time, traceback, textwrap
|
||||
|
||||
from PyQt4.Qt import SIGNAL, QObject, Qt, QTimer, QThread, QDate, \
|
||||
QPixmap, QListWidgetItem, QDialog, pyqtSignal
|
||||
@ -331,6 +328,14 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
||||
ResizableDialog.__init__(self, window)
|
||||
self.bc_box.layout().setAlignment(self.cover, Qt.AlignCenter|Qt.AlignHCenter)
|
||||
self.cancel_all = False
|
||||
base = unicode(self.author_sort.toolTip())
|
||||
self.ok_aus_tooltip = '<p>' + textwrap.fill(base+'<br><br>'+
|
||||
_(' The green color indicates that the current '
|
||||
'author sort matches the current author'))
|
||||
self.bad_aus_tooltip = '<p>'+textwrap.fill(base + '<br><br>'+
|
||||
_(' The red color indicates that the current '
|
||||
'author sort does not match the current author'))
|
||||
|
||||
if cancel_all:
|
||||
self.__abort_button = self.button_box.addButton(self.button_box.Abort)
|
||||
self.__abort_button.setToolTip(_('Abort the editing of all remaining books'))
|
||||
@ -375,6 +380,10 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
||||
self.remove_unused_series)
|
||||
QObject.connect(self.auto_author_sort, SIGNAL('clicked()'),
|
||||
self.deduce_author_sort)
|
||||
self.connect(self.author_sort, SIGNAL('textChanged(const QString&)'),
|
||||
self.author_sort_box_changed)
|
||||
self.connect(self.authors, SIGNAL('editTextChanged(const QString&)'),
|
||||
self.authors_box_changed)
|
||||
self.connect(self.formats, SIGNAL('itemDoubleClicked(QListWidgetItem*)'),
|
||||
self.show_format)
|
||||
self.connect(self.formats, SIGNAL('delete_format()'), self.remove_format)
|
||||
@ -467,6 +476,28 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
||||
for c in range(2, len(ans[i].widgets), 2):
|
||||
w.setTabOrder(ans[i].widgets[c-1], ans[i].widgets[c+1])
|
||||
|
||||
def authors_box_changed(self, txt):
|
||||
aus = unicode(txt)
|
||||
aus = re.sub(r'\s+et al\.$', '', aus)
|
||||
aus = self.db.author_sort_from_authors(string_to_authors(aus))
|
||||
self.mark_author_sort(normal=(unicode(self.author_sort.text()) == aus))
|
||||
|
||||
def author_sort_box_changed(self, txt):
|
||||
au = unicode(self.authors.text())
|
||||
au = re.sub(r'\s+et al\.$', '', au)
|
||||
au = self.db.author_sort_from_authors(string_to_authors(au))
|
||||
self.mark_author_sort(normal=(au == txt))
|
||||
|
||||
def mark_author_sort(self, normal=True):
|
||||
if normal:
|
||||
col = 'rgb(0, 255, 0, 20%)'
|
||||
else:
|
||||
col = 'rgb(255, 0, 0, 20%)'
|
||||
self.author_sort.setStyleSheet('QLineEdit { color: black; '
|
||||
'background-color: %s; }'%col)
|
||||
tt = self.ok_aus_tooltip if normal else self.bad_aus_tooltip
|
||||
self.author_sort.setToolTip(tt)
|
||||
|
||||
def validate_isbn(self, isbn):
|
||||
isbn = unicode(isbn).strip()
|
||||
if not isbn:
|
||||
|
@ -151,14 +151,16 @@
|
||||
<item>
|
||||
<widget class="EnLineEdit" name="author_sort">
|
||||
<property name="toolTip">
|
||||
<string>Specify how the author(s) of this book should be sorted. For example Charles Dickens should be sorted as Dickens, Charles.</string>
|
||||
<string>Specify how the author(s) of this book should be sorted. For example Charles Dickens should be sorted as Dickens, Charles.
|
||||
If the box is colored green, then text matches the individual author's sort strings. If it is colored red, then the authors and this text do not match.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="auto_author_sort">
|
||||
<property name="toolTip">
|
||||
<string>Automatically create the author sort entry based on the current author entry</string>
|
||||
<string>Automatically create the author sort entry based on the current author entry.
|
||||
Using this button to create author sort will change author sort from red to green.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
|
@ -61,7 +61,7 @@ class LocationManager(QObject): # {{{
|
||||
|
||||
ac('library', _('Library'), 'lt.png',
|
||||
_('Show books in calibre library'))
|
||||
ac('main', _('Reader'), 'reader.png',
|
||||
ac('main', _('Device'), 'reader.png',
|
||||
_('Show books in the main memory of the device'))
|
||||
ac('carda', _('Card A'), 'sd.png',
|
||||
_('Show books in storage card A'))
|
||||
@ -197,11 +197,21 @@ class SearchBar(QWidget): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class Spacer(QWidget):
|
||||
|
||||
def __init__(self, parent):
|
||||
QWidget.__init__(self, parent)
|
||||
self.l = QHBoxLayout()
|
||||
self.setLayout(self.l)
|
||||
self.l.addStretch(10)
|
||||
|
||||
|
||||
class ToolBar(QToolBar): # {{{
|
||||
|
||||
def __init__(self, donate, location_manager, parent):
|
||||
def __init__(self, donate, location_manager, child_bar, parent):
|
||||
QToolBar.__init__(self, parent)
|
||||
self.gui = parent
|
||||
self.child_bar = child_bar
|
||||
self.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||
self.setMovable(False)
|
||||
self.setFloatable(False)
|
||||
@ -223,16 +233,19 @@ class ToolBar(QToolBar): # {{{
|
||||
sz = gprefs['toolbar_icon_size']
|
||||
sz = {'small':24, 'medium':48, 'large':64}[sz]
|
||||
self.setIconSize(QSize(sz, sz))
|
||||
self.child_bar.setIconSize(QSize(sz, sz))
|
||||
style = Qt.ToolButtonTextUnderIcon
|
||||
if gprefs['toolbar_text'] == 'never':
|
||||
style = Qt.ToolButtonIconOnly
|
||||
self.setToolButtonStyle(style)
|
||||
self.child_bar.setToolButtonStyle(style)
|
||||
self.donate_button.set_normal_icon_size(sz, sz)
|
||||
|
||||
def contextMenuEvent(self, *args):
|
||||
pass
|
||||
|
||||
def build_bar(self):
|
||||
self.child_bar.setVisible(gprefs['show_child_bar'])
|
||||
self.showing_donate = False
|
||||
showing_device = self.location_manager.has_device
|
||||
actions = '-device' if showing_device else ''
|
||||
@ -244,10 +257,16 @@ class ToolBar(QToolBar): # {{{
|
||||
m.setVisible(False)
|
||||
|
||||
self.clear()
|
||||
self.child_bar.clear()
|
||||
self.added_actions = []
|
||||
self.spacers = [Spacer(self.child_bar), Spacer(self.child_bar),
|
||||
Spacer(self), Spacer(self)]
|
||||
self.child_bar.addWidget(self.spacers[0])
|
||||
if gprefs['show_child_bar']:
|
||||
self.addWidget(self.spacers[2])
|
||||
|
||||
for what in actions:
|
||||
if what is None:
|
||||
if what is None and not gprefs['show_child_bar']:
|
||||
self.addSeparator()
|
||||
elif what == 'Location Manager':
|
||||
for ac in self.location_manager.available_actions:
|
||||
@ -262,12 +281,21 @@ class ToolBar(QToolBar): # {{{
|
||||
self.showing_donate = True
|
||||
elif what in self.gui.iactions:
|
||||
action = self.gui.iactions[what]
|
||||
self.addAction(action.qaction)
|
||||
bar = self
|
||||
if action.action_type == 'current' and gprefs['show_child_bar']:
|
||||
bar = self.child_bar
|
||||
bar.addAction(action.qaction)
|
||||
self.added_actions.append(action.qaction)
|
||||
self.setup_tool_button(action.qaction, action.popup_type)
|
||||
|
||||
self.child_bar.addWidget(self.spacers[1])
|
||||
if gprefs['show_child_bar']:
|
||||
self.addWidget(self.spacers[3])
|
||||
|
||||
def setup_tool_button(self, ac, menu_mode=None):
|
||||
ch = self.widgetForAction(ac)
|
||||
if ch is None:
|
||||
ch = self.child_bar.widgetForAction(ac)
|
||||
ch.setCursor(Qt.PointingHandCursor)
|
||||
ch.setAutoRaise(True)
|
||||
if ac.menu() is not None and menu_mode is not None:
|
||||
@ -280,7 +308,8 @@ class ToolBar(QToolBar): # {{{
|
||||
if p == 'never':
|
||||
style = Qt.ToolButtonIconOnly
|
||||
|
||||
if p == 'auto' and self.preferred_width > self.width()+35:
|
||||
if p == 'auto' and self.preferred_width > self.width()+35 and \
|
||||
not gprefs['show_child_bar']:
|
||||
style = Qt.ToolButtonIconOnly
|
||||
|
||||
self.setToolButtonStyle(style)
|
||||
@ -309,9 +338,11 @@ class MainWindowMixin(object): # {{{
|
||||
self.iactions['Fetch News'].init_scheduler(db)
|
||||
|
||||
self.search_bar = SearchBar(self)
|
||||
self.child_bar = QToolBar(self)
|
||||
self.tool_bar = ToolBar(self.donate_button,
|
||||
self.location_manager, self)
|
||||
self.location_manager, self.child_bar, self)
|
||||
self.addToolBar(Qt.TopToolBarArea, self.tool_bar)
|
||||
self.addToolBar(Qt.BottomToolBarArea, self.child_bar)
|
||||
|
||||
l = self.centralwidget.layout()
|
||||
l.addWidget(self.search_bar)
|
||||
|
@ -1027,7 +1027,9 @@ class DeviceBooksModel(BooksModel): # {{{
|
||||
def resort(self, reset=True):
|
||||
if self.sorted_on:
|
||||
self.sort(self.column_map.index(self.sorted_on[0]),
|
||||
self.sorted_on[1], reset=reset)
|
||||
self.sorted_on[1], reset=False)
|
||||
if reset:
|
||||
self.reset()
|
||||
|
||||
def columnCount(self, parent):
|
||||
if parent and parent.isValid():
|
||||
|
@ -46,6 +46,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
r('use_roman_numerals_for_series_number', config)
|
||||
r('separate_cover_flow', config, restart_required=True)
|
||||
r('search_as_you_type', config)
|
||||
r('show_child_bar', gprefs)
|
||||
|
||||
choices = [(_('Small'), 'small'), (_('Medium'), 'medium'),
|
||||
(_('Large'), 'large')]
|
||||
|
@ -173,6 +173,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_show_child_bar">
|
||||
<property name="text">
|
||||
<string>&Split the toolbar into two toolbars</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -376,7 +376,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
'series' : QIcon(I('series.png')),
|
||||
'formats' : QIcon(I('book.png')),
|
||||
'publisher' : QIcon(I('publisher.png')),
|
||||
'rating' : QIcon(I('star.png')),
|
||||
'rating' : QIcon(I('rating.png')),
|
||||
'news' : QIcon(I('news.png')),
|
||||
'tags' : QIcon(I('tags.png')),
|
||||
':custom' : QIcon(I('column.png')),
|
||||
|
@ -2523,6 +2523,10 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
# Fetch the database as a dictionary
|
||||
self.booksBySeries = self.plugin.search_sort_db(self.db, self.opts)
|
||||
if not self.booksBySeries:
|
||||
self.opts.generate_series = False
|
||||
self.opts.log(" no series found in selected books, cancelling series generation")
|
||||
return
|
||||
|
||||
friendly_name = "Series"
|
||||
|
||||
@ -2586,7 +2590,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
aTag = Tag(soup, 'a')
|
||||
aTag['name'] = "%s_series" % re.sub('\W','',book['series']).lower()
|
||||
pSeriesTag.insert(0,aTag)
|
||||
pSeriesTag.insert(1,NavigableString(self.NOT_READ_SYMBOL + '%s' % book['series']))
|
||||
pSeriesTag.insert(1,NavigableString('%s' % book['series']))
|
||||
divTag.insert(dtc,pSeriesTag)
|
||||
dtc += 1
|
||||
|
||||
@ -2595,7 +2599,14 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
ptc = 0
|
||||
|
||||
# book with read/reading/unread symbol
|
||||
if 'read' in book and book['read']:
|
||||
for tag in book['tags']:
|
||||
if tag == self.opts.read_tag:
|
||||
book['read'] = True
|
||||
break
|
||||
else:
|
||||
book['read'] = False
|
||||
|
||||
if book['read']:
|
||||
# check mark
|
||||
pBookTag.insert(ptc,NavigableString(self.READ_SYMBOL))
|
||||
pBookTag['class'] = "read_book"
|
||||
|
@ -597,8 +597,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
return identical_book_ids
|
||||
|
||||
def has_cover(self, index, index_is_id=False):
|
||||
id = index if index_is_id else self.id(index)
|
||||
path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg')
|
||||
id = index if index_is_id else self.id(index)
|
||||
try:
|
||||
path = os.path.join(self.abspath(id, index_is_id=True), 'cover.jpg')
|
||||
except:
|
||||
# Can happen if path has not yet been set
|
||||
return False
|
||||
return os.access(path, os.R_OK)
|
||||
|
||||
def remove_cover(self, id, notify=True):
|
||||
@ -609,6 +613,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
except (IOError, OSError):
|
||||
time.sleep(0.2)
|
||||
os.remove(path)
|
||||
self.data.set(id, self.FIELD_MAP['cover'], False, row_is_id=True)
|
||||
if notify:
|
||||
self.notify('cover', [id])
|
||||
|
||||
@ -629,6 +634,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
except (IOError, OSError):
|
||||
time.sleep(0.2)
|
||||
save_cover_data_to(data, path)
|
||||
self.data.set(id, self.FIELD_MAP['cover'], True, row_is_id=True)
|
||||
if notify:
|
||||
self.notify('cover', [id])
|
||||
|
||||
@ -1087,8 +1093,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
self.set_path(id, True)
|
||||
self.notify('metadata', [id])
|
||||
|
||||
# Given a book, return the list of author sort strings for the book's authors
|
||||
def authors_sort_strings(self, id, index_is_id=False):
|
||||
'''
|
||||
Given a book, return the list of author sort strings
|
||||
for the book's authors
|
||||
'''
|
||||
id = id if index_is_id else self.id(id)
|
||||
aut_strings = self.conn.get('''
|
||||
SELECT sort
|
||||
@ -1744,10 +1753,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
series_index = 1.0 if mi.series_index is None else mi.series_index
|
||||
aus = mi.author_sort if mi.author_sort else self.author_sort_from_authors(mi.authors)
|
||||
title = mi.title
|
||||
if isinstance(aus, str):
|
||||
if isbytestring(aus):
|
||||
aus = aus.decode(preferred_encoding, 'replace')
|
||||
if isinstance(title, str):
|
||||
title = title.decode(preferred_encoding)
|
||||
if isbytestring(title):
|
||||
title = title.decode(preferred_encoding, 'replace')
|
||||
obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)',
|
||||
(title, series_index, aus))
|
||||
id = obj.lastrowid
|
||||
|
@ -330,6 +330,17 @@ There are a few more options in this section.
|
||||
two covers. This option will simply remove the first image from the source document, thereby
|
||||
ensuring that the converted book has only one cover, the one specified in |app|.
|
||||
|
||||
:guilabel:`Preprocess input`
|
||||
This option activates various algorithms that try to detect and correct common cases of
|
||||
badly formatted input documents. Things like hard line breaks, large blocks of text with no formatting, etc.
|
||||
Turn this option on if your input document suffers from bad formatting. But be aware that in
|
||||
some cases, this option can lead to worse results, so use with care.
|
||||
|
||||
:guilabel:`Line-unwrap factor`
|
||||
This option control the algorithm |app| uses to remove hard line breaks. For example, if the value of this
|
||||
option is 0.4, that means calibre will remove hard line breaks from the end of lines whose lengths are less
|
||||
than the length of 40% of all lines in the document.
|
||||
|
||||
Table of Contents
|
||||
------------------
|
||||
|
||||
|
@ -194,7 +194,7 @@ class Image(_magick.Image): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
def create_canvas(width, height, bgcolor='white'):
|
||||
def create_canvas(width, height, bgcolor='#ffffff'):
|
||||
canvas = Image()
|
||||
canvas.create_canvas(int(width), int(height), str(bgcolor))
|
||||
return canvas
|
||||
|
@ -5,12 +5,14 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
|
||||
from calibre.utils.magick import Image, DrawingWand, create_canvas
|
||||
from calibre.constants import __appname__, __version__
|
||||
from calibre import fit_image
|
||||
|
||||
def save_cover_data_to(data, path, bgcolor='white', resize_to=None):
|
||||
def save_cover_data_to(data, path, bgcolor='#ffffff', resize_to=None,
|
||||
return_data=False):
|
||||
'''
|
||||
Saves image in data to path, in the format specified by the path
|
||||
extension. Composes the image onto a blank canvas so as to
|
||||
@ -22,9 +24,11 @@ def save_cover_data_to(data, path, bgcolor='white', resize_to=None):
|
||||
img.size = (resize_to[0], resize_to[1])
|
||||
canvas = create_canvas(img.size[0], img.size[1], bgcolor)
|
||||
canvas.compose(img)
|
||||
if return_data:
|
||||
return canvas.export(os.path.splitext(path)[1][1:])
|
||||
canvas.save(path)
|
||||
|
||||
def thumbnail(data, width=120, height=120, bgcolor='white', fmt='jpg'):
|
||||
def thumbnail(data, width=120, height=120, bgcolor='#ffffff', fmt='jpg'):
|
||||
img = Image()
|
||||
img.load(data)
|
||||
owidth, oheight = img.size
|
||||
@ -57,7 +61,7 @@ def identify(path):
|
||||
return identify_data(data)
|
||||
|
||||
def add_borders_to_image(path_to_image, left=0, top=0, right=0, bottom=0,
|
||||
border_color='white'):
|
||||
border_color='#ffffff'):
|
||||
img = Image()
|
||||
img.open(path_to_image)
|
||||
lwidth, lheight = img.size
|
||||
@ -76,7 +80,7 @@ def create_text_wand(font_size, font_path=None):
|
||||
ans.text_alias = True
|
||||
return ans
|
||||
|
||||
def create_text_arc(text, font_size, font=None, bgcolor='white'):
|
||||
def create_text_arc(text, font_size, font=None, bgcolor='#ffffff'):
|
||||
if isinstance(text, unicode):
|
||||
text = text.encode('utf-8')
|
||||
|
||||
@ -144,7 +148,7 @@ class TextLine(object):
|
||||
|
||||
|
||||
def create_cover_page(top_lines, logo_path, width=590, height=750,
|
||||
bgcolor='white', output_format='jpg'):
|
||||
bgcolor='#ffffff', output_format='jpg'):
|
||||
'''
|
||||
Create the standard calibre cover page and return it as a byte string in
|
||||
the specified output_format.
|
||||
|
@ -290,10 +290,12 @@ class BasicNewsRecipe(Recipe):
|
||||
#: the cover for the periodical. Overriding this in your recipe instructs
|
||||
#: calibre to render the downloaded cover into a frame whose width and height
|
||||
#: are expressed as a percentage of the downloaded cover.
|
||||
#: cover_margins = (10,15,'white') pads the cover with a white margin
|
||||
#: cover_margins = (10, 15, '#ffffff') pads the cover with a white margin
|
||||
#: 10px on the left and right, 15px on the top and bottom.
|
||||
#: Colors name defined at http://www.imagemagick.org/script/color.php
|
||||
cover_margins = (0,0,'white')
|
||||
#: Color names defined at http://www.imagemagick.org/script/color.php
|
||||
#: Note that for some reason, white does not always work on windows. Use
|
||||
#: #ffffff instead
|
||||
cover_margins = (0, 0, '#ffffff')
|
||||
|
||||
#: Set to a non empty string to disable this recipe
|
||||
#: The string will be used as the disabled message
|
||||
|
Loading…
x
Reference in New Issue
Block a user