diff --git a/recipes/kath_net.recipe b/recipes/kath_net.recipe
index d6155270c0..9a21b18a7e 100644
--- a/recipes/kath_net.recipe
+++ b/recipes/kath_net.recipe
@@ -11,12 +11,10 @@ class AdvancedUserRecipe1295262156(BasicNewsRecipe):
auto_cleanup = True
encoding='iso-8859-1'
-
feeds = [(u'kath.net', u'http://www.kath.net/2005/xml/index.xml')]
-
def print_version(self, url):
- return url+"&print=yes"
+ return url+"/print/yes"
extra_css = 'td.textb {font-size: medium;}'
diff --git a/recipes/midday.recipe b/recipes/midday.recipe
deleted file mode 100644
index 4dbee1d2f3..0000000000
--- a/recipes/midday.recipe
+++ /dev/null
@@ -1,13 +0,0 @@
-from calibre.web.feeds.news import CalibrePeriodical
-
-class MiDDay(CalibrePeriodical):
-
- title = 'MiDDay'
- calibre_periodicals_slug = 'midday'
-
- description = '''Get your dose of the latest news, views and fun - from the
- world of politics, sports and Bollywood to the cartoons, comics and games of
- the entertainment section - India’s leading tabloid has it all. To subscribe
- visit calibre
- Periodicals.'''
- language = 'en_IN'
diff --git a/recipes/nytimesbook.recipe b/recipes/nytimesbook.recipe
index ad20586770..2d8fb69a7e 100644
--- a/recipes/nytimesbook.recipe
+++ b/recipes/nytimesbook.recipe
@@ -35,7 +35,10 @@ class NewYorkTimesBookReview(BasicNewsRecipe):
continue
if x['class'] in {'story', 'ledeStory'}:
tt = 'h3' if x['class'] == 'story' else 'h1'
- a = x.find(tt).find('a', href=True)
+ try:
+ a = x.find(tt).find('a', href=True)
+ except AttributeError:
+ continue
title = self.tag_to_string(a)
url = a['href'] + '&pagewanted=all'
self.log('\tFound article:', title, url)
diff --git a/src/calibre/devices/misc.py b/src/calibre/devices/misc.py
index d7a4531fc7..d8a8ed1871 100644
--- a/src/calibre/devices/misc.py
+++ b/src/calibre/devices/misc.py
@@ -413,16 +413,16 @@ class WAYTEQ(USBMS):
name = 'WayteQ device interface'
gui_name = 'WayteQ xBook'
- description = _('Communicate with the WayteQ Reader')
+ description = _('Communicate with the WayteQ and SPC Dickens Readers')
author = 'Kovid Goyal'
supported_platforms = ['windows', 'osx', 'linux']
# Ordered list of supported formats
FORMATS = ['epub', 'mobi', 'prc', 'fb2', 'txt', 'pdf', 'html', 'rtf', 'chm', 'djvu', 'doc']
- VENDOR_ID = [0x05e3]
- PRODUCT_ID = [0x0726]
- BCD = [0x0222]
+ VENDOR_ID = [0x05e3, 0x0c45]
+ PRODUCT_ID = [0x0726, 0x0184]
+ BCD = [0x0222, 0x0100]
EBOOK_DIR_MAIN = 'Documents'
SCAN_FROM_ROOT = True
@@ -431,6 +431,14 @@ class WAYTEQ(USBMS):
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'RK28_SDK_DEMO'
SUPPORTS_SUB_DIRS = True
+ def get_gui_name(self):
+ try:
+ if self.detected_device.idVendor == 0x0c45:
+ return 'SPC Dickens'
+ except Exception:
+ pass
+ return self.gui_name
+
def get_carda_ebook_dir(self, for_upload=False):
if for_upload:
return 'Documents'
diff --git a/src/calibre/devices/mtp/unix/devices.c b/src/calibre/devices/mtp/unix/devices.c
index b6d50bac5b..2efe02c38f 100644
--- a/src/calibre/devices/mtp/unix/devices.c
+++ b/src/calibre/devices/mtp/unix/devices.c
@@ -11,18 +11,6 @@
const calibre_device_entry_t calibre_mtp_device_table[] = {
#include "upstream/music-players.h"
- // Amazon Kindle Fire HD
- , { "Amazon", 0x1949, "Fire HD", 0x0007, DEVICE_FLAGS_ANDROID_BUGS}
- , { "Amazon", 0x1949, "Fire HD", 0x0008, DEVICE_FLAGS_ANDROID_BUGS}
- , { "Amazon", 0x1949, "Fire HD", 0x000a, DEVICE_FLAGS_ANDROID_BUGS}
-
- // Nexus 10
- , { "Google", 0x18d1, "Nexus 10", 0x4ee2, DEVICE_FLAGS_ANDROID_BUGS}
- , { "Google", 0x18d1, "Nexus 10", 0x4ee1, DEVICE_FLAGS_ANDROID_BUGS}
-
- // Kobo Arc
- , { "Kobo", 0x2237, "Arc", 0xd108, DEVICE_FLAGS_ANDROID_BUGS}
-
, { NULL, 0xffff, NULL, 0xffff, DEVICE_FLAG_NONE }
};
diff --git a/src/calibre/devices/mtp/unix/upstream/music-players.h b/src/calibre/devices/mtp/unix/upstream/music-players.h
index 33c7b1f926..c7a9b80bce 100644
--- a/src/calibre/devices/mtp/unix/upstream/music-players.h
+++ b/src/calibre/devices/mtp/unix/upstream/music-players.h
@@ -294,6 +294,13 @@
DEVICE_FLAG_UNIQUE_FILENAMES |
DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST },
// The "YP-R2" (0x04e8/0x512d) is NOT MTP, it is UMS only.
+ // Guessing on device flags for the MTP mode...
+ { "Samsung", 0x04e8, "YP-R2", 0x512e,
+ DEVICE_FLAG_UNLOAD_DRIVER |
+ DEVICE_FLAG_OGG_IS_UNKNOWN |
+ DEVICE_FLAG_UNIQUE_FILENAMES |
+ DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST |
+ DEVICE_FLAG_PLAYLIST_SPL_V1 },
// From Manuel Carro
// Copied from Q2
{ "Samsung", 0x04e8, "YP-Q3", 0x5130,
@@ -309,6 +316,7 @@
DEVICE_FLAG_OGG_IS_UNKNOWN |
DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST |
DEVICE_FLAG_PLAYLIST_SPL_V1 },
+ // YP-F3 is NOT MTP - USB mass storage
// From a rouge .INF file
// this device ID seems to have been recycled for:
// the Samsung SGH-A707 Cingular cellphone
@@ -393,7 +401,9 @@
DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST |
DEVICE_FLAG_UNLOAD_DRIVER |
DEVICE_FLAG_LONG_TIMEOUT |
- DEVICE_FLAG_PROPLIST_OVERRIDES_OI },
+ DEVICE_FLAG_PROPLIST_OVERRIDES_OI |
+ DEVICE_FLAG_OGG_IS_UNKNOWN |
+ DEVICE_FLAG_FLAC_IS_UNKNOWN },
// Reported by David Goodenough
// Guessing on flags.
{ "Samsung", 0x04e8, "Galaxy Y", 0x685e,
@@ -401,14 +411,18 @@
DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST |
DEVICE_FLAG_UNLOAD_DRIVER |
DEVICE_FLAG_LONG_TIMEOUT |
- DEVICE_FLAG_PROPLIST_OVERRIDES_OI },
+ DEVICE_FLAG_PROPLIST_OVERRIDES_OI |
+ DEVICE_FLAG_OGG_IS_UNKNOWN |
+ DEVICE_FLAG_FLAC_IS_UNKNOWN },
{ "Samsung", 0x04e8,
"Galaxy models (MTP)", 0x6860,
DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST_ALL |
DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST |
DEVICE_FLAG_UNLOAD_DRIVER |
DEVICE_FLAG_LONG_TIMEOUT |
- DEVICE_FLAG_PROPLIST_OVERRIDES_OI },
+ DEVICE_FLAG_PROPLIST_OVERRIDES_OI |
+ DEVICE_FLAG_OGG_IS_UNKNOWN |
+ DEVICE_FLAG_FLAC_IS_UNKNOWN },
// From: Erik Berglund
// Logs indicate this needs DEVICE_FLAG_NO_ZERO_READS
// No Samsung platlists on this device.
@@ -419,7 +433,9 @@
{ "Samsung", 0x04e8, "Galaxy models Kies mode", 0x6877,
DEVICE_FLAG_UNLOAD_DRIVER |
DEVICE_FLAG_LONG_TIMEOUT |
- DEVICE_FLAG_PROPLIST_OVERRIDES_OI },
+ DEVICE_FLAG_PROPLIST_OVERRIDES_OI |
+ DEVICE_FLAG_OGG_IS_UNKNOWN |
+ DEVICE_FLAG_FLAC_IS_UNKNOWN },
// From: John Gorkos and
// Akos Maroy
{ "Samsung", 0x04e8, "Vibrant SGH-T959/Captivate/Media player mode", 0x68a9,
@@ -439,7 +455,6 @@
*/
{ "Microsoft/Intel", 0x045e, "Bandon Portable Media Center", 0x00c9,
DEVICE_FLAG_NONE },
- // Reported by anonymous sourceforge user
// HTC Mozart is using the PID, as is Nokia Lumia 800
// May need MTPZ to work
{ "Microsoft", 0x045e, "Windows Phone", 0x04ec, DEVICE_FLAG_NONE },
@@ -450,12 +465,12 @@
{ "Microsoft", 0x045e, "Windows MTP Simulator", 0x0622, DEVICE_FLAG_NONE },
// Reported by Edward Hutchins (used for Zune HDs)
{ "Microsoft", 0x045e, "Zune HD", 0x063e, DEVICE_FLAG_NONE },
- // Reported by anonymous sourceforge user
{ "Microsoft", 0x045e, "Kin 1", 0x0640, DEVICE_FLAG_NONE },
- // Reported by anonymous sourceforge user
{ "Microsoft/Sharp/nVidia", 0x045e, "Kin TwoM", 0x0641, DEVICE_FLAG_NONE },
// Reported by Farooq Zaman (used for all Zunes)
{ "Microsoft", 0x045e, "Zune", 0x0710, DEVICE_FLAG_NONE },
+ // Reported by Olegs Jeremejevs
+ { "Microsoft/HTC", 0x045e, "HTC 8S", 0xf0ca, DEVICE_FLAG_NONE },
/*
* JVC
@@ -517,33 +532,52 @@
// From Anonymous SourceForge User
{ "Philips", 0x0471, "GoGear Vibe/02", 0x20e5,
DEVICE_FLAG_UNLOAD_DRIVER },
+ // Reported by Philip Rhoades
+ { "Philips", 0x0471, "GoGear Ariaz/97", 0x2138,
+ DEVICE_FLAG_UNLOAD_DRIVER },
// from XNJB user
{ "Philips", 0x0471, "PSA235", 0x7e01, DEVICE_FLAG_NONE },
/*
* Acer
+ * Reporters:
+ * Franck VDL
+ * Matthias Arndt
+ * Arvin Schnell
+ * Philippe Marzouk
+ * nE0sIghT
+ * Maxime de Roucy
*/
- // Reported by anonymous sourceforge user
- { "Acer", 0x0502, "Iconia TAB A500 (ID1)", 0x3325, DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by: Franck VDL
- { "Acer", 0x0502, "Iconia TAB A500 (ID2)", 0x3341, DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by: Matthias Arndt
- { "Acer", 0x0502, "Iconia TAB A501", 0x3344, DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by: anonymous sourceforge user
- { "Acer", 0x0502, "Iconia TAB A100 (ID1)", 0x3348, DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by: Arvin Schnell
- { "Acer", 0x0502, "Iconia TAB A100 (ID2)", 0x3349, DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by Philippe Marzouk
- { "Acer", 0x0502, "Iconia TAB A700", 0x3378, DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by anonymous sourceforge user
- { "Acer", 0x0502, "Iconia TAB A200 (ID1)", 0x337c, DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by anonymous sourceforge user
- { "Acer", 0x0502, "Iconia TAB A200 (ID2)", 0x337d, DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by nE0sIghT
- { "Acer", 0x0502, "Iconia TAB A510", 0x338a, DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by Maxime de Roucy
+ { "Acer", 0x0502, "Iconia TAB A500 (ID1)", 0x3325,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Acer", 0x0502, "Iconia TAB A500 (ID2)", 0x3341,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Acer", 0x0502, "Iconia TAB A501 (ID1)", 0x3344,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Acer", 0x0502, "Iconia TAB A501 (ID2)", 0x3345,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Acer", 0x0502, "Iconia TAB A100 (ID1)", 0x3348,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Acer", 0x0502, "Iconia TAB A100 (ID2)", 0x3349,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Acer", 0x0502, "Iconia TAB A700", 0x3378,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Acer", 0x0502, "Iconia TAB A200 (ID1)", 0x337c,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Acer", 0x0502, "Iconia TAB A200 (ID2)", 0x337d,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Acer", 0x0502, "Iconia TAB A510 (ID1)", 0x3389,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Acer", 0x0502, "Iconia TAB A510 (ID2)", 0x338a,
+ DEVICE_FLAGS_ANDROID_BUGS },
{ "Acer", 0x0502, "E350 Liquid Gallant Duo", 0x33c3,
DEVICE_FLAGS_ANDROID_BUGS },
+ { "Acer", 0x0502, "Iconia TAB A210", 0x33cb,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Acer", 0x0502, "Iconia TAB A110", 0x33d8,
+ DEVICE_FLAGS_ANDROID_BUGS },
+
+
/*
* SanDisk
@@ -743,7 +777,11 @@
{ "iRiver", 0x4102, "E50", 0x1151,
DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST | DEVICE_FLAG_NO_ZERO_READS |
DEVICE_FLAG_OGG_IS_UNKNOWN },
- // Reported by Jakub Matraszek
+ // Reported by anonymous SourceForge user, guessing on flags
+ { "iRiver", 0x4102, "E150", 0x1152,
+ DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST | DEVICE_FLAG_NO_ZERO_READS |
+ DEVICE_FLAG_OGG_IS_UNKNOWN },
+ // Reported by Jakub Matraszek
{ "iRiver", 0x4102, "T5", 0x1153,
DEVICE_FLAG_UNLOAD_DRIVER | DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST |
DEVICE_FLAG_NO_ZERO_READS | DEVICE_FLAG_OGG_IS_UNKNOWN },
@@ -877,8 +915,10 @@
{ "Archos", 0x0e79, "SPOD (MTP mode)", 0x1341, DEVICE_FLAG_UNLOAD_DRIVER },
{ "Archos", 0x0e79, "5S IT (MTP mode)", 0x1351, DEVICE_FLAG_UNLOAD_DRIVER },
{ "Archos", 0x0e79, "5H IT (MTP mode)", 0x1357, DEVICE_FLAG_UNLOAD_DRIVER },
- { "Archos", 0x0e79, "Arnova Childpad", 0x1458, DEVICE_FLAG_UNLOAD_DRIVER },
- // Reported by anonymous Sourceforge user
+ { "Archos", 0x0e79, "Arnova Childpad", 0x1458, DEVICE_FLAGS_ANDROID_BUGS },
+ { "Archos", 0x0e79, "Arnova 8c G3", 0x145e, DEVICE_FLAGS_ANDROID_BUGS },
+ { "Archos", 0x0e79, "Arnova 10bG3 Tablet", 0x146b, DEVICE_FLAGS_ANDROID_BUGS },
+ { "Archos", 0x0e79, "97 Xenon", 0x149a, DEVICE_FLAGS_ANDROID_BUGS },
{ "Archos", 0x0e79, "8o G9 (MTP mode)", 0x1508, DEVICE_FLAG_UNLOAD_DRIVER },
// Reported by Clément
{ "Archos", 0x0e79, "8o G9 Turbo (MTP mode)", 0x1509,
@@ -887,14 +927,10 @@
{ "Archos", 0x0e79, "80G9", 0x1518, DEVICE_FLAGS_ANDROID_BUGS },
// Reported by Till
{ "Archos", 0x0e79, "101 G9", 0x1528, DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by anonymous sourceforge user
{ "Archos", 0x0e79, "101 G9 (v2)", 0x1529, DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by anonymous sourceforge user
{ "Archos", 0x0e79, "101 G9 Turbo 250 HD", 0x1538,
DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by anonymous sourceforge user
{ "Archos", 0x0e79, "101 G9 Turbo", 0x1539, DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by anonymous sourceforge user
{ "Archos", 0x0e79, "70it2 (mode 1)", 0x1568, DEVICE_FLAGS_ANDROID_BUGS },
// Reported by Sebastien ROHAUT
{ "Archos", 0x0e79, "70it2 (mode 2)", 0x1569, DEVICE_FLAGS_ANDROID_BUGS },
@@ -1075,6 +1111,10 @@
DEVICE_FLAG_UNLOAD_DRIVER },
// From: Maxin B. John
{ "Nokia", 0x0421, "N9", 0x051a, DEVICE_FLAG_NONE },
+ { "Nokia", 0x0421, "C5-00", 0x0592, DEVICE_FLAG_NONE },
+ // Reported by Sampo Savola
+ // Covers Lumia 920, 820 and probably any WP8 device.
+ { "Nokia", 0x0421, "Nokia Lumia WP8", 0x0661, DEVICE_FLAG_NONE },
// Reported by Richard Wall
{ "Nokia", 0x05c6, "5530 Xpressmusic", 0x0229, DEVICE_FLAG_NONE },
// Reported by anonymous SourceForge user
@@ -1109,9 +1149,12 @@
{ "Thomson / RCA", 0x069b, "Lyra HC308A", 0x3035, DEVICE_FLAG_NONE },
/*
- * NTT DoCoMo
+ * Fujitsu devices
*/
- { "FOMA", 0x04c5, "F903iX HIGH-SPEED", 0x1140, DEVICE_FLAG_NONE },
+ { "Fujitsu, Ltd", 0x04c5, "F903iX HIGH-SPEED", 0x1140, DEVICE_FLAG_NONE },
+ // Reported by Thomas Bretthauer
+ { "Fujitsu, Ltd", 0x04c5, "STYLISTIC M532", 0x133b,
+ DEVICE_FLAGS_ANDROID_BUGS },
/*
* Palm device userland program named Pocket Tunes
@@ -1254,7 +1297,6 @@
/*
* LG Electronics
*/
- // From anonymous SourceForge user
// Uncertain if this is really the MTP mode device ID...
{ "LG Electronics Inc.", 0x043e, "T54", 0x7040,
DEVICE_FLAG_UNLOAD_DRIVER },
@@ -1271,20 +1313,20 @@
{ "LG Electronics Inc.", 0x1004, "GR-500 Music Player", 0x611b,
DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST |
DEVICE_FLAG_ALWAYS_PROBE_DESCRIPTOR },
- // Reported by anonymous sourceforge user
{ "LG Electronics Inc.", 0x1004, "KM900", 0x6132,
DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST |
DEVICE_FLAG_UNLOAD_DRIVER },
- // Reported by anonymous sourceforge user
{ "LG Electronics Inc.", 0x1004, "LG8575", 0x619a,
DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST |
DEVICE_FLAG_UNLOAD_DRIVER },
- // Reported by anonymous sourceforge user
{ "LG Electronics Inc.", 0x1004, "V909 G-Slate", 0x61f9,
DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST |
DEVICE_FLAG_UNLOAD_DRIVER },
+ { "LG Electronics Inc.", 0x1004, "LG2 Optimus", 0x6225,
+ DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST |
+ DEVICE_FLAG_UNLOAD_DRIVER },
// Reported by Brian J. Murrell
- { "LG Electronics Inc.", 0x1004, "LG-E617G/P700", 0x631c,
+ { "LG Electronics Inc.", 0x1004, "LG-E610/E612/E617G/E970/P700", 0x631c,
DEVICE_FLAGS_ANDROID_BUGS },
/*
@@ -1294,69 +1336,69 @@
* reported to see a pattern here.
*/
// Reported by Alessandro Radaelli
- { "Sony", 0x054c, "Walkman NWZ-A815/NWZ-A818", 0x0325,
+ { "Sony", 0x054c, "NWZ-A815/NWZ-A818", 0x0325,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by anonymous Sourceforge user.
- { "Sony", 0x054c, "Walkman NWZ-S516", 0x0326,
+ { "Sony", 0x054c, "NWZ-S516", 0x0326,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Endre Oma
- { "Sony", 0x054c, "Walkman NWZ-S615F/NWZ-S616F/NWZ-S618F", 0x0327,
+ { "Sony", 0x054c, "NWZ-S615F/NWZ-S616F/NWZ-S618F", 0x0327,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Jean-Marc Bourguet
- { "Sony", 0x054c, "Walkman NWZ-S716F", 0x035a,
+ { "Sony", 0x054c, "NWZ-S716F", 0x035a,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Anon SF User / Anthon van der Neut
- { "Sony", 0x054c, "Walkman NWZ-A826/NWZ-A828/NWZ-A829", 0x035b,
+ { "Sony", 0x054c, "NWZ-A826/NWZ-A828/NWZ-A829", 0x035b,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Niek Klaverstijn
- { "Sony", 0x054c, "Walkman NWZ-A726/NWZ-A728/NWZ-A768", 0x035c,
+ { "Sony", 0x054c, "NWZ-A726/NWZ-A728/NWZ-A768", 0x035c,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Mehdi AMINI
- { "Sony", 0x054c, "Walkman NWZ-B135", 0x036e,
+ { "Sony", 0x054c, "NWZ-B135", 0x036e,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by
- { "Sony", 0x054c, "Walkman NWZ-E436F", 0x0385,
+ { "Sony", 0x054c, "NWZ-E436F", 0x0385,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Michael Wilkinson
- { "Sony", 0x054c, "Walkman NWZ-W202", 0x0388,
+ { "Sony", 0x054c, "NWZ-W202", 0x0388,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Ondrej Sury
- { "Sony", 0x054c, "Walkman NWZ-S739F", 0x038c,
+ { "Sony", 0x054c, "NWZ-S739F", 0x038c,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Marco Filipe Nunes Soares Abrantes Pereira
- { "Sony", 0x054c, "Walkman NWZ-S638F", 0x038e,
+ { "Sony", 0x054c, "NWZ-S638F", 0x038e,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Elliot
- { "Sony", 0x054c, "Walkman NWZ-X1050B/NWZ-X1060B",
+ { "Sony", 0x054c, "NWZ-X1050B/NWZ-X1060B",
0x0397, DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Silvio J. Gutierrez
- { "Sony", 0x054c, "Walkman NWZ-X1051/NWZ-X1061", 0x0398,
+ { "Sony", 0x054c, "NWZ-X1051/NWZ-X1061", 0x0398,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Gregory Boddin
- { "Sony", 0x054c, "Walkman NWZ-B142F", 0x03d8,
+ { "Sony", 0x054c, "NWZ-B142F", 0x03d8,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Rick Warner
- { "Sony", 0x054c, "Walkman NWZ-E344", 0x03fc,
+ { "Sony", 0x054c, "NWZ-E344/E345", 0x03fc,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Jonathan Stowe
- { "Sony", 0x054c, "Walkman NWZ-E445", 0x03fd,
+ { "Sony", 0x054c, "NWZ-E445", 0x03fd,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Anonymous SourceForge user
- { "Sony", 0x054c, "Walkman NWZ-S545", 0x03fe,
+ { "Sony", 0x054c, "NWZ-S545", 0x03fe,
DEVICE_FLAGS_SONY_NWZ_BUGS },
- { "Sony", 0x054c, "Walkman NWZ-A845", 0x0404,
+ { "Sony", 0x054c, "NWZ-A845", 0x0404,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by anonymous SourceForge user
- { "Sony", 0x054c, "Walkman NWZ-W252B", 0x04bb,
+ { "Sony", 0x054c, "NWZ-W252B", 0x04bb,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Suspect this device has strong DRM features
// See https://answers.launchpad.net/ubuntu/+source/libmtp/+question/149587
- { "Sony", 0x054c, "Walkman NWZ-B153F", 0x04be,
+ { "Sony", 0x054c, "NWZ-B153F", 0x04be,
DEVICE_FLAGS_SONY_NWZ_BUGS },
- { "Sony", 0x054c, "Walkman NWZ-E354", 0x04cb,
+ { "Sony", 0x054c, "NWZ-E354", 0x04cb,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Toni Burgarello
- { "Sony", 0x054c, "Walkman NWZ-S754", 0x04cc,
+ { "Sony", 0x054c, "NWZ-S754", 0x04cc,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Hideki Yamane
{ "Sony", 0x054c, "Sony Tablet P1", 0x04d1,
@@ -1364,9 +1406,7 @@
// Reported by dmiceman
{ "Sony", 0x054c, "NWZ-B163F", 0x059a,
DEVICE_FLAGS_SONY_NWZ_BUGS },
- // Reported by anonymous Sourceforge user
- // guessing on device flags...
- { "Sony", 0x054c, "Walkman NWZ-E464", 0x05a6,
+ { "Sony", 0x054c, "NWZ-E464", 0x05a6,
DEVICE_FLAGS_SONY_NWZ_BUGS },
// Reported by Jan Rheinlaender
{ "Sony", 0x054c, "NWZ-S765", 0x05a8,
@@ -1377,7 +1417,8 @@
// Reported by ghalambaz
{ "Sony", 0x054c, "Sony Tablet S1", 0x05b4,
DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by Anonymous SourceForge user
+ { "Sony", 0x054c, "NWZ-B173F", 0x0689,
+ DEVICE_FLAGS_SONY_NWZ_BUGS },
{ "Sony", 0x054c, "DCR-SR75", 0x1294,
DEVICE_FLAGS_SONY_NWZ_BUGS },
@@ -1496,6 +1537,7 @@
* Jean-François B.
* Eduard Bloch
* Ah Hong
+ * Eowyn Carter
*/
{ "SonyEricsson", 0x0fce, "LT15i Xperia arc S MTP", 0x014f,
DEVICE_FLAG_NONE },
@@ -1503,6 +1545,8 @@
DEVICE_FLAG_NONE },
{ "SonyEricsson", 0x0fce, "MK16i Xperia MTP", 0x015a,
DEVICE_FLAG_NONE },
+ { "SonyEricsson", 0x0fce, "R800/R88i Xperia Play MTP", 0x015d,
+ DEVICE_FLAG_NONE },
{ "SonyEricsson", 0x0fce, "ST18a Xperia Ray MTP", 0x0161,
DEVICE_FLAG_NONE },
{ "SonyEricsson", 0x0fce, "SK17i Xperia Mini Pro MTP", 0x0166,
@@ -1533,12 +1577,26 @@
DEVICE_FLAG_NONE },
{ "SONY", 0x0fce, "ST27i/ST27a Xperia go MTP", 0x017e,
DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "ST23i Xperia Miro MTP", 0x0180,
+ DEVICE_FLAG_NONE },
{ "SONY", 0x0fce, "SO-05D Xperia SX MTP", 0x0181,
DEVICE_FLAG_NONE },
{ "SONY", 0x0fce, "LT30p Xperia T MTP", 0x0182,
DEVICE_FLAG_NONE },
{ "SONY", 0x0fce, "LT25i Xperia V MTP", 0x0186,
DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia J MTP", 0x0188,
+ DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia ZL MTP", 0x0189,
+ DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia E MTP", 0x018c,
+ DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia Tablet Z MTP", 0x018D,
+ DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia Z MTP", 0x0193,
+ DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia Tablet Z MTP", 0x0194,
+ DEVICE_FLAG_NONE },
/*
* MTP+UMS personalities of MTP devices (see above)
@@ -1565,12 +1623,26 @@
DEVICE_FLAG_NONE },
{ "SONY", 0x0fce, "ST27i/ST27a Xperia go MTP+CDROM", 0x417e,
DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "ST23i Xperia Miro MTP+CDROM", 0x4180,
+ DEVICE_FLAG_NONE },
{ "SONY", 0x0fce, "SO-05D Xperia SX MTP+CDROM", 0x4181,
DEVICE_FLAG_NONE },
{ "SONY", 0x0fce, "LT30p Xperia T MTP+CDROM", 0x4182,
DEVICE_FLAG_NONE },
{ "SONY", 0x0fce, "LT25i Xperia V MTP+CDROM", 0x4186,
DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia J MTP+CDROM", 0x4188,
+ DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia ZL MTP", 0x4189,
+ DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia E MTP+CDROM", 0x418c,
+ DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia Tablet Z MTP", 0x418d,
+ DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia Z MTP", 0x4193,
+ DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia Tablet Z MTP", 0x4194,
+ DEVICE_FLAG_NONE },
/*
* MTP+ADB personalities of MTP devices (see above)
@@ -1579,20 +1651,20 @@
DEVICE_FLAG_NONE },
{ "SonyEricsson", 0x0fce, "MT11i Xperia Neo MTP+ADB", 0x5156,
DEVICE_FLAG_NONE },
- { "SonyEricsson", 0x0fce, "ST17i Xperia Active MTP+ADB", 0x5168,
- DEVICE_FLAG_NONE },
- { "SONY", 0x0fce, "LT26i Xperia S MTP+ADB", 0x5169,
- DEVICE_FLAG_NO_ZERO_READS },
{ "SonyEricsson", 0x0fce, "MK16i Xperia MTP+ADB", 0x515a,
DEVICE_FLAG_NONE },
+ { "SonyEricsson", 0x0fce, "R800/R88i Xperia Play MTP+ADB", 0x515d,
+ DEVICE_FLAG_NONE },
{ "SonyEricsson", 0x0fce, "ST18i Xperia Ray MTP+ADB", 0x5161,
DEVICE_FLAG_NONE },
{ "SonyEricsson", 0x0fce, "SK17i Xperia Mini Pro MTP+ADB", 0x5166,
DEVICE_FLAG_NONE },
{ "SonyEricsson", 0x0fce, "ST15i Xperia Mini MTP+ADB", 0x5167,
DEVICE_FLAG_NONE },
- { "SonyEricsson", 0x0fce, "LT26i Xperia S MTP+ADB", 0x5169,
+ { "SonyEricsson", 0x0fce, "ST17i Xperia Active MTP+ADB", 0x5168,
DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "LT26i Xperia S MTP+ADB", 0x5169,
+ DEVICE_FLAG_NO_ZERO_READS },
{ "SonyEricsson", 0x0fce, "SK17i Xperia Mini Pro MTP+ADB", 0x516d,
DEVICE_FLAG_NONE },
{ "SONY", 0x0fce, "ST21i Xperia Tipo MTP+ADB", 0x5170,
@@ -1613,12 +1685,26 @@
DEVICE_FLAG_NONE },
{ "SONY", 0x0fce, "ST27i/ST27a Xperia go MTP+ADB", 0x517e,
DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "ST23i Xperia Miro MTP+ADB", 0x5180,
+ DEVICE_FLAG_NONE },
{ "SONY", 0x0fce, "SO-05D Xperia SX MTP+ADB", 0x5181,
DEVICE_FLAG_NONE },
{ "SONY", 0x0fce, "LT30p Xperia T MTP+ADB", 0x5182,
DEVICE_FLAG_NONE },
{ "SONY", 0x0fce, "LT25i Xperia V MTP+ADB", 0x5186,
DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia J MTP+ADB", 0x5188,
+ DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia ZL MTP", 0x5189,
+ DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia E MTP+ADB", 0x518c,
+ DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia Tablet Z MTP", 0x518d,
+ DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia Z MTP", 0x5193,
+ DEVICE_FLAG_NONE },
+ { "SONY", 0x0fce, "Xperia Tablet Z MTP", 0x5194,
+ DEVICE_FLAG_NONE },
/*
* MTP+UMS modes
@@ -1661,17 +1747,23 @@
{ "Motorola", 0x22b8, "V3m/V750 verizon", 0x2a65,
DEVICE_FLAG_BROKEN_SET_OBJECT_PROPLIST |
DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST_ALL },
+ { "Motorola", 0x22b8, "Atrix/Razr HD (MTP)", 0x2e32,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Motorola", 0x22b8, "Atrix/Razr HD (MTP+ADB)", 0x2e33,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Motorola", 0x22b8, "RAZR M (XT907)", 0x2e51,
+ DEVICE_FLAGS_ANDROID_BUGS },
// Reported by Jader Rodrigues Simoes
{ "Motorola", 0x22b8, "Xoom 2 Media Edition (ID2)", 0x41cf,
DEVICE_FLAGS_ANDROID_BUGS },
// Reported by Steven Roemen
{ "Motorola", 0x22b8, "Droid X/MB525 (Defy)", 0x41d6,
DEVICE_FLAG_NONE },
- // Reported by anonymous user
+ { "Motorola", 0x22b8, "DROID2 (ID1)", 0x41da,
+ DEVICE_FLAG_NONE },
{ "Motorola", 0x22b8, "Milestone / Verizon Droid", 0x41dc,
DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by anonymous user
- { "Motorola", 0x22b8, "DROID2", 0x42a7,
+ { "Motorola", 0x22b8, "DROID2 (ID2)", 0x42a7,
DEVICE_FLAGS_ANDROID_BUGS },
{ "Motorola", 0x22b8, "Xoom 2 Media Edition", 0x4311,
DEVICE_FLAGS_ANDROID_BUGS },
@@ -1693,6 +1785,9 @@
// Reported by anonymous user
{ "Motorola", 0x22b8, "RAZR2 V8/U9/Z6", 0x6415,
DEVICE_FLAG_BROKEN_SET_OBJECT_PROPLIST },
+ // Reported by Brian Dolbec
+ { "Motorola", 0x22b8, "Atrix MB860 (MTP)", 0x7088,
+ DEVICE_FLAGS_ANDROID_BUGS },
/*
* Motorola Xoom (Wingray) variants
*
@@ -1719,9 +1814,15 @@
DEVICE_FLAGS_ANDROID_BUGS },
{ "Motorola", 0x22b8, "Xoom (MTP+ADB)", 0x70a9,
DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by anonymous Sourceforge user
// "carried by C Spire and other CDMA US carriers"
- { "Motorola", 0x22b8, "Milestone X2", 0x70ca, DEVICE_FLAGS_ANDROID_BUGS },
+ { "Motorola", 0x22b8, "Milestone X2", 0x70ca,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Motorola", 0x22b8, "XT890/907 (MTP)", 0x710d,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Motorola", 0x22b8, "XT890/907 (MTP+ADB)", 0x710e,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Motorola", 0x22b8, "XT890/907 (MTP+?)", 0x710f,
+ DEVICE_FLAGS_ANDROID_BUGS },
/*
* Google
@@ -1729,23 +1830,32 @@
* road to produce an Android tablet it seems... The Vendor ID
* was originally used for Nexus phones
*/
+ { "Google Inc (for Ainol Novo)", 0x18d1, "Fire/Flame", 0x0007,
+ DEVICE_FLAGS_ANDROID_BUGS },
{ "Google Inc (for Sony)", 0x18d1, "S1", 0x05b3,
DEVICE_FLAGS_ANDROID_BUGS },
// Reported by anonymous Sourceforge user
{ "Google Inc (for Barnes & Noble)", 0x18d1, "Nook Color", 0x2d02,
DEVICE_FLAGS_ANDROID_BUGS },
// Reported by anonymous Sourceforge user
+ { "Google Inc (for Asus)", 0x18d1, "TF201 Transformer", 0x4d00,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ // Reported by anonymous Sourceforge user
{ "Google Inc (for Asus)", 0x18d1, "TF101 Transformer", 0x4e0f,
DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by Laurent Artaud
- { "Google Inc (for Samsung)", 0x18d1, "Nexus S", 0x4e21,
- DEVICE_FLAGS_ANDROID_BUGS },
+ // 0x4e21 (Nexus S) is a USB Mass Storage device.
// Reported by Chris Smith
{ "Google Inc (for Asus)", 0x18d1, "Nexus 7 (MTP)", 0x4e41,
DEVICE_FLAGS_ANDROID_BUGS },
// Reported by Michael Hess
{ "Google Inc (for Asus)", 0x18d1, "Nexus 7 (MTP+ADB)", 0x4e42,
DEVICE_FLAGS_ANDROID_BUGS },
+ { "Google Inc (for LG Electronics/Samsung)", 0x18d1,
+ "Nexus 4/10 (MTP)", 0x4ee1,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Google Inc (for LG Electronics/Samsung)", 0x18d1,
+ "Nexus 4/10 (MTP+ADB)", 0x4ee2,
+ DEVICE_FLAGS_ANDROID_BUGS },
// WiFi-only version of Xoom
// See: http://bugzilla.gnome.org/show_bug.cgi?id=647506
{ "Google Inc (for Motorola)", 0x18d1, "Xoom (MZ604)", 0x70a8,
@@ -1845,9 +1955,16 @@
*/
{ "Coby", 0x1e74, "COBY MP705", 0x6512, DEVICE_FLAG_NONE },
+#if 0
/*
* Apple devices, which are not MTP natively but can be made to speak MTP
* using PwnTunes (http://www.pwntunes.net/)
+ * CURRENTLY COMMENTED OUT:
+ * These will make the UDEV rules flag these as MTP devices even if
+ * PwnTunes is NOT installed. That is unacceptable, so a better solution
+ * that actually inspects if the device has PwnTunes/MTP support needs
+ * to be found, see:
+ * https://sourceforge.net/p/libmtp/bugs/759/
*/
{ "Apple", 0x05ac, "iPhone", 0x1290, DEVICE_FLAG_NONE },
{ "Apple", 0x05ac, "iPod Touch 1st Gen", 0x1291, DEVICE_FLAG_NONE },
@@ -1859,6 +1976,7 @@
{ "Apple", 0x05ac, "0x1298", 0x1298, DEVICE_FLAG_NONE },
{ "Apple", 0x05ac, "iPod Touch 3rd Gen", 0x1299, DEVICE_FLAG_NONE },
{ "Apple", 0x05ac, "iPad", 0x129a, DEVICE_FLAG_NONE },
+#endif
// Reported by anonymous SourceForge user, also reported as
// Pantech Crux, claming to be:
@@ -1872,30 +1990,48 @@
/*
* Asus
+ * Pattern of PIDs on Android devices seem to be:
+ * n+0 = MTP
+ * n+1 = MTP+ADB
+ * n+2 = ?
+ * n+3 = ?
+ * n+4 = PTP
*/
// Reported by Glen Overby
- { "Asus", 0x0b05, "TF300 Transformer", 0x4c80,
+ { "Asus", 0x0b05, "TF300 Transformer (MTP)", 0x4c80,
DEVICE_FLAGS_ANDROID_BUGS },
// Reported by jaile
- { "Asus", 0x0b05, "TF300 Transformer (USB debug mode)", 0x4c81,
+ { "Asus", 0x0b05, "TF300 Transformer (MTP+ADB)", 0x4c81,
DEVICE_FLAGS_ANDROID_BUGS },
// Repored by Florian Apolloner
- { "Asus", 0x0b05, "TF700 Transformer", 0x4c90,
+ { "Asus", 0x0b05, "TF700 Transformer (MTP)", 0x4c90,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Asus", 0x0b05, "TF700 Transformer (MTP+ADB)", 0x4c91,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Asus", 0x0b05, "MeMo Pad Smart 10", 0x4cd0,
DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by anonymous Sourceforge user
{ "Asus", 0x0b05, "TF201 Transformer Prime (keyboard dock)", 0x4d00,
DEVICE_FLAGS_ANDROID_BUGS },
{ "Asus", 0x0b05, "TF201 Transformer Prime (tablet only)", 0x4d01,
DEVICE_FLAGS_ANDROID_BUGS },
- { "Asus", 0x0b05, "TFXXX Transformer Prime (unknown version)", 0x4d04,
+ // 4d04 is the PTP mode, don't add it
+ { "Asus", 0x0b05, "SL101 (MTP)", 0x4e00,
DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by anonymous Sourceforge user
- { "Asus", 0x0b05, "TF101 Eeepad Slider", 0x4e01,
+ { "Asus", 0x0b05, "SL101 (MTP+ADB)", 0x4e01,
DEVICE_FLAGS_ANDROID_BUGS },
- { "Asus", 0x0b05, "TF101 Eeepad Transformer", 0x4e0f,
+ { "Asus", 0x0b05, "TF101 Eeepad Transformer (MTP)", 0x4e0f,
DEVICE_FLAGS_ANDROID_BUGS },
- { "Asus", 0x0b05, "TF101 Eeepad Transformer (debug mode)", 0x4e1f,
+ { "Asus", 0x0b05, "TF101 Eeepad Transformer (MTP+ADB)", 0x4e1f,
DEVICE_FLAGS_ANDROID_BUGS },
+ { "Asus", 0x0b05, "PadFone (MTP)", 0x5200,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Asus", 0x0b05, "PadFone (MTP+ADB)", 0x5201,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Asus", 0x0b05, "PadFone 2 (MTP+?)", 0x5210,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Asus", 0x0b05, "PadFone 2 (MTP)", 0x5211,
+ DEVICE_FLAGS_ANDROID_BUGS },
+
/*
@@ -1914,17 +2050,26 @@
// Reported by: anonymous sourceforge user
{ "Lenovo", 0x17ef, "Lifetab S9512", 0x74cc,
DEVICE_FLAGS_ANDROID_BUGS },
+ // Reported by Brian J. Murrell
+ { "Lenovo", 0x17ef, "IdeaTab A2109A", 0x7542,
+ DEVICE_FLAGS_ANDROID_BUGS },
/*
* Huawei
*/
// Reported by anonymous SourceForge user
- { "Huawei", 0x12d1, "Honor U8860", 0x1051, DEVICE_FLAGS_ANDROID_BUGS },
+ { "Huawei", 0x12d1, "Honor U8860", 0x1051,
+ DEVICE_FLAGS_ANDROID_BUGS },
// Reported by anonymous SourceForge user
- { "Huawei", 0x12d1, "Mediapad (mode 0)", 0x360f, DEVICE_FLAGS_ANDROID_BUGS },
+ { "Huawei", 0x12d1, "U8815/U9200", 0x1052,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ // Reported by anonymous SourceForge user
+ { "Huawei", 0x12d1, "Mediapad (mode 0)", 0x360f,
+ DEVICE_FLAGS_ANDROID_BUGS },
// Reported by Bearsh
- { "Huawei", 0x12d1, "Mediapad (mode 1)", 0x361f, DEVICE_FLAGS_ANDROID_BUGS },
+ { "Huawei", 0x12d1, "Mediapad (mode 1)", 0x361f,
+ DEVICE_FLAGS_ANDROID_BUGS },
/*
* ZTE
@@ -1932,27 +2077,61 @@
*/
{ "ZTE", 0x19d2, "V55 ID 1", 0x0244, DEVICE_FLAGS_ANDROID_BUGS },
{ "ZTE", 0x19d2, "V55 ID 2", 0x0245, DEVICE_FLAGS_ANDROID_BUGS },
+ { "ZTE", 0x19d2, "v790/Blade 3", 0x0306, DEVICE_FLAGS_ANDROID_BUGS },
/*
* HTC (High Tech Computer Corp)
+ * Reporters:
+ * Steven Eastland
+ * Kevin Cheng
*/
- { "HTC", 0x0bb4, "Zopo ZP100 (ID1)", 0x0c02,
+#if 0
+ /*
+ * This had to be commented out - the same VID+PID is used also for
+ * other modes than MTP, so we need to let mtp-probe do its job on this
+ * device instead of adding it to the database.
+ */
+ { "HTC", 0x0bb4, "Android Device ID1 (Zopo, HD2, Bird...)", 0x0c02,
DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by Steven Eastland
- { "HTC", 0x0bb4, "EVO 4G LTE", 0x0c93,
+#endif
+ { "HTC", 0x0bb4, "EVO 4G LTE/One V (ID1)", 0x0c93,
DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by Steven Eastland
- { "HTC", 0x0bb4, "EVO 4G LTE (second ID)", 0x0ca8,
+ { "HTC", 0x0bb4, "EVO 4G LTE/One V (ID2)", 0x0ca8,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "HTC", 0x0bb4, "HTC One S (ID1)", 0x0cec,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "HTC", 0x0bb4, "HTC Evo 4G LTE (ID1)", 0x0df5,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "HTC", 0x0bb4, "HTC One S (ID2)", 0x0df9,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "HTC", 0x0bb4, "HTC One X (ID1)", 0x0dfb,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "HTC", 0x0bb4, "HTC One X (ID2)", 0x0dfc,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "HTC", 0x0bb4, "HTC One X (ID3)", 0x0dfd,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "HTC", 0x0bb4, "HTC Butterfly (ID1)", 0x0dfe,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "HTC", 0x0bb4, "Droid DNA (MTP+UMS+ADB)", 0x0dff,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "HTC", 0x0bb4, "HTC Droid Incredible 4G LTE (MTP)", 0x0e31,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "HTC", 0x0bb4, "HTC Droid Incredible 4G LTE (MTP+ADB)", 0x0e32,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "HTC", 0x0bb4, "Droid DNA (MTP+UMS)", 0x0ebd,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "HTC", 0x0bb4, "HTC One X (ID2)", 0x0f91,
DEVICE_FLAGS_ANDROID_BUGS },
// These identify themselves as "cm_tenderloin", fun...
// Done by HTC for HP I guess.
- { "Hewlett-Packard", 0x0bb4, "HP Touchpad", 0x685c,
+ { "Hewlett-Packard", 0x0bb4, "HP Touchpad (MTP)", 0x685c,
DEVICE_FLAGS_ANDROID_BUGS },
- { "Hewlett-Packard", 0x0bb4, "HP Touchpad (debug mode)",
- 0x6860, DEVICE_FLAGS_ANDROID_BUGS },
- // Reported by anonymous SourceForge user
- { "HTC", 0x0bb4, "Zopo ZP100 (ID2)", 0x2008,
+ { "Hewlett-Packard", 0x0bb4, "HP Touchpad (MTP+ADB)", 0x6860,
DEVICE_FLAGS_ANDROID_BUGS },
+#if 0
+ { "HTC", 0x0bb4, "Android Device ID2 (Zopo, HD2...)", 0x2008,
+ DEVICE_FLAGS_ANDROID_BUGS },
+#endif
/*
* NEC
@@ -1963,7 +2142,12 @@
* nVidia
*/
// Found on Internet forum
- { "nVidia", 0x0955, "CM9-Adam", 0x70a9, DEVICE_FLAGS_ANDROID_BUGS },
+ { "nVidia", 0x0955, "CM9-Adam", 0x70a9,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "nVidia", 0x0955, "Nabi2 Tablet (ID1)", 0x7100,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "nVidia", 0x0955, "Nabi2 Tablet (ID2)", 0x7102,
+ DEVICE_FLAGS_ANDROID_BUGS },
/*
* Vizio
@@ -1972,10 +2156,60 @@
{ "Vizio", 0x0489, "VTAB1008", 0xe040, DEVICE_FLAGS_ANDROID_BUGS },
/*
- * Viewpia, bq...
- * Seems like some multi-branded OEM product.
+ * Amazon
*/
- { "Various", 0x2207, "Viewpia DR/bq Kepler", 0x0001, DEVICE_FLAGS_ANDROID_BUGS },
+ { "Amazon", 0x1949, "Kindle Fire 2G (ID1)", 0x0005,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Amazon", 0x1949, "Kindle Fire (ID1)", 0x0007,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Amazon", 0x1949, "Kindle Fire (ID2)", 0x0008,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Amazon", 0x1949, "Kindle Fire (ID3)", 0x000a,
+ DEVICE_FLAGS_ANDROID_BUGS },
+
+ /*
+ * Barnes&Noble
+ */
+ { "Barnes&Noble", 0x2080, "Nook HD+", 0x0005,
+ DEVICE_FLAGS_ANDROID_BUGS },
+
+ /*
+ * Viewpia, bq, YiFang
+ * Seems like some multi-branded OEM product line.
+ */
+ { "Various", 0x2207, "Viewpia DR/bq Kepler", 0x0001,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "YiFang", 0x2207, "BQ Tesla", 0x0006,
+ DEVICE_FLAGS_ANDROID_BUGS },
+
+ /*
+ * Kobo
+ */
+ // Reported by George Talusan
+ { "Kobo", 0x2237, "Arc (ID1)", 0xd108,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Kobo", 0x2237, "Arc (ID2)", 0xd109,
+ DEVICE_FLAGS_ANDROID_BUGS },
+
+ /*
+ * Hisense
+ */
+ // Reported by Anonymous SourceForge user
+ { "Hisense", 0x109b, "E860", 0x9109, DEVICE_FLAGS_ANDROID_BUGS },
+
+ /*
+ * Intel
+ * Also sold rebranded as Orange products
+ */
+ { "Intel", 0x8087, "Xolo 900/AZ210A", 0x09fb, DEVICE_FLAGS_ANDROID_BUGS },
+
+ /*
+ * Xiaomi
+ */
+ { "Xiaomi", 0x2717, "Mi-2 (MTP+ADB)", 0x9039,
+ DEVICE_FLAGS_ANDROID_BUGS },
+ { "Xiaomi", 0x2717, "Mi-2 (MTP)", 0xf003,
+ DEVICE_FLAGS_ANDROID_BUGS },
/*
* Other strange stuff.
diff --git a/src/calibre/devices/mtp/unix/upstream/update.py b/src/calibre/devices/mtp/unix/upstream/update.py
index 20c03d072d..50ae2becd7 100644
--- a/src/calibre/devices/mtp/unix/upstream/update.py
+++ b/src/calibre/devices/mtp/unix/upstream/update.py
@@ -7,14 +7,16 @@ __license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-MP = 'http://libmtp.git.sourceforge.net/git/gitweb.cgi?p=libmtp/libmtp;a=blob_plain;f=src/music-players.h;hb=HEAD'
-DF = 'http://libmtp.git.sourceforge.net/git/gitweb.cgi?p=libmtp/libmtp;a=blob_plain;f=src/device-flags.h;hb=HEAD'
-
-import urllib, os, shutil
+import os, shutil, subprocess
base = os.path.dirname(os.path.abspath(__file__))
-for url, fname in [(MP, 'music-players.h'), (DF, 'device-flags.h')]:
- with open(os.path.join(base, fname), 'wb') as f:
- shutil.copyfileobj(urllib.urlopen(url), f)
+os.chdir('/tmp')
+if os.path.exists('libmtp'):
+ shutil.rmtree('libmtp')
+subprocess.check_call(['git', 'clone', 'git://git.code.sf.net/p/libmtp/code',
+ 'libmtp'])
+for x in ('src/music-players.h', 'src/device-flags.h'):
+ with open(os.path.join(base, os.path.basename(x)), 'wb') as f:
+ shutil.copyfileobj(open('libmtp/'+x), f)
diff --git a/src/calibre/ebooks/conversion/plugins/html_input.py b/src/calibre/ebooks/conversion/plugins/html_input.py
index f00ccb9d9b..558b4636b4 100644
--- a/src/calibre/ebooks/conversion/plugins/html_input.py
+++ b/src/calibre/ebooks/conversion/plugins/html_input.py
@@ -7,7 +7,7 @@ __license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import re, tempfile, os, imghdr
+import re, tempfile, os
from functools import partial
from itertools import izip
from urllib import quote
@@ -17,6 +17,7 @@ from calibre.customize.conversion import (InputFormatPlugin,
OptionRecommendation)
from calibre.utils.localization import get_lang
from calibre.utils.filenames import ascii_filename
+from calibre.utils.imghdr import what
class HTMLInput(InputFormatPlugin):
@@ -250,7 +251,7 @@ class HTMLInput(InputFormatPlugin):
if media_type == self.BINARY_MIME:
# Check for the common case, images
try:
- img = imghdr.what(link)
+ img = what(link)
except EnvironmentError:
pass
else:
diff --git a/src/calibre/ebooks/conversion/plugins/rtf_input.py b/src/calibre/ebooks/conversion/plugins/rtf_input.py
index 9249ea8d48..45d7f16608 100644
--- a/src/calibre/ebooks/conversion/plugins/rtf_input.py
+++ b/src/calibre/ebooks/conversion/plugins/rtf_input.py
@@ -105,7 +105,7 @@ class RTFInput(InputFormatPlugin):
return f.read()
def extract_images(self, picts):
- import imghdr
+ from calibre.utils.imghdr import what
self.log('Extracting images...')
with open(picts, 'rb') as f:
@@ -120,7 +120,7 @@ class RTFInput(InputFormatPlugin):
if len(enc) % 2 == 1:
enc = enc[:-1]
data = enc.decode('hex')
- fmt = imghdr.what(None, data)
+ fmt = what(None, data)
if fmt is None:
fmt = 'wmf'
count += 1
diff --git a/src/calibre/ebooks/metadata/mobi.py b/src/calibre/ebooks/metadata/mobi.py
index ab475f33a8..7ad9a01962 100644
--- a/src/calibre/ebooks/metadata/mobi.py
+++ b/src/calibre/ebooks/metadata/mobi.py
@@ -9,7 +9,7 @@ __copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net and ' \
'Marshall T. Vandegrift '
__docformat__ = 'restructuredtext en'
-import os, cStringIO, imghdr
+import os, cStringIO
from struct import pack, unpack
from cStringIO import StringIO
@@ -18,12 +18,13 @@ from calibre.ebooks.mobi import MobiError, MAX_THUMB_DIMEN
from calibre.ebooks.mobi.utils import rescale_image
from calibre.ebooks.mobi.langcodes import iana2mobi
from calibre.utils.date import now as nowf
+from calibre.utils.imghdr import what
from calibre.utils.localization import canonicalize_lang, lang_as_iso639_1
def is_image(ss):
if ss is None:
return False
- return imghdr.what(None, ss[:200]) is not None
+ return what(None, ss[:200]) is not None
class StreamSlicer(object):
diff --git a/src/calibre/ebooks/mobi/debug/mobi8.py b/src/calibre/ebooks/mobi/debug/mobi8.py
index 213e15cf85..e1c8ffba44 100644
--- a/src/calibre/ebooks/mobi/debug/mobi8.py
+++ b/src/calibre/ebooks/mobi/debug/mobi8.py
@@ -8,7 +8,7 @@ __license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import sys, os, imghdr, struct, textwrap
+import sys, os, struct, textwrap
from itertools import izip
from calibre import CurrentDir
@@ -18,6 +18,7 @@ from calibre.ebooks.mobi.debug.index import (SKELIndex, SECTIndex, NCXIndex,
from calibre.ebooks.mobi.utils import read_font_record, decode_tbs, RECORD_SIZE
from calibre.ebooks.mobi.debug import format_bytes
from calibre.ebooks.mobi.reader.headers import NULL_INDEX
+from calibre.utils.imghdr import what
class FDST(object):
@@ -173,7 +174,7 @@ class MOBIFile(object):
font['raw_data'])
prefix, ext = 'fonts', font['ext']
elif sig not in known_types:
- q = imghdr.what(None, rec.raw)
+ q = what(None, rec.raw)
if q:
prefix, ext = 'images', q
diff --git a/src/calibre/ebooks/mobi/reader/mobi8.py b/src/calibre/ebooks/mobi/reader/mobi8.py
index 8938b103d3..a55f6bd7e3 100644
--- a/src/calibre/ebooks/mobi/reader/mobi8.py
+++ b/src/calibre/ebooks/mobi/reader/mobi8.py
@@ -7,7 +7,7 @@ __license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import struct, re, os, imghdr
+import struct, re, os
from collections import namedtuple
from itertools import repeat, izip
from urlparse import urldefrag
@@ -23,6 +23,7 @@ from calibre.ebooks.metadata.toc import TOC
from calibre.ebooks.mobi.utils import read_font_record
from calibre.ebooks.oeb.parse_utils import parse_html
from calibre.ebooks.oeb.base import XPath, XHTML, xml2text
+from calibre.utils.imghdr import what
Part = namedtuple('Part',
'num type filename start end aid')
@@ -403,7 +404,7 @@ class Mobi8Reader(object):
if font['encrypted']:
self.encrypted_fonts.append(href)
else:
- imgtype = imghdr.what(None, data)
+ imgtype = what(None, data)
if imgtype is None:
imgtype = 'unknown'
href = 'images/%05d.%s'%(fname_idx, imgtype)
diff --git a/src/calibre/ebooks/mobi/utils.py b/src/calibre/ebooks/mobi/utils.py
index 09e3055a6e..e9bc4f669f 100644
--- a/src/calibre/ebooks/mobi/utils.py
+++ b/src/calibre/ebooks/mobi/utils.py
@@ -7,11 +7,12 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import struct, string, imghdr, zlib, os
+import struct, string, zlib, os
from collections import OrderedDict
from io import BytesIO
from calibre.utils.magick.draw import Image, save_cover_data_to, thumbnail
+from calibre.utils.imghdr import what
from calibre.ebooks import normalize
IMAGE_MAX_SIZE = 10 * 1024 * 1024
@@ -384,9 +385,9 @@ def to_base(num, base=32, min_num_digits=None):
def mobify_image(data):
'Convert PNG images to GIF as the idiotic Kindle cannot display some PNG'
- what = imghdr.what(None, data)
+ fmt = what(None, data)
- if what == 'png':
+ if fmt == 'png':
im = Image()
im.load(data)
data = im.export('gif')
diff --git a/src/calibre/ebooks/mobi/writer2/resources.py b/src/calibre/ebooks/mobi/writer2/resources.py
index bdf20a6f2c..01ce6a0135 100644
--- a/src/calibre/ebooks/mobi/writer2/resources.py
+++ b/src/calibre/ebooks/mobi/writer2/resources.py
@@ -7,13 +7,12 @@ __license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import imghdr
-
from calibre.ebooks.mobi import MAX_THUMB_DIMEN, MAX_THUMB_SIZE
from calibre.ebooks.mobi.utils import (rescale_image, mobify_image,
write_font_record)
from calibre.ebooks import generate_masthead
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
+from calibre.utils.imghdr import what
PLACEHOLDER_GIF = b'GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\xff\xff\xff!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00@\x02\x01D\x00;'
@@ -84,7 +83,7 @@ class Resources(object):
self.image_indices.add(len(self.records))
self.records.append(data)
self.item_map[item.href] = index
- self.mime_map[item.href] = 'image/%s'%imghdr.what(None, data)
+ self.mime_map[item.href] = 'image/%s'%what(None, data)
index += 1
if cover_href and item.href == cover_href:
diff --git a/src/calibre/ebooks/oeb/polish/toc.py b/src/calibre/ebooks/oeb/polish/toc.py
index d5b013fb72..3a72b837c8 100644
--- a/src/calibre/ebooks/oeb/polish/toc.py
+++ b/src/calibre/ebooks/oeb/polish/toc.py
@@ -15,7 +15,7 @@ from functools import partial
from lxml import etree
from calibre import __version__
-from calibre.ebooks.oeb.base import XPath, uuid_id, xml2text, NCX, NCX_NS, XML
+from calibre.ebooks.oeb.base import XPath, uuid_id, xml2text, NCX, NCX_NS, XML, XHTML
from calibre.ebooks.oeb.polish.container import guess_type
from calibre.utils.localization import get_lang, canonicalize_lang, lang_as_iso639_1
@@ -39,10 +39,17 @@ class TOC(object):
c.parent = self
return c
+ def remove(self, child):
+ self.children.remove(child)
+ child.parent = None
+
def __iter__(self):
for c in self.children:
yield c
+ def __len__(self):
+ return len(self.children)
+
def iterdescendants(self):
for child in self:
yield child
@@ -169,6 +176,91 @@ def get_toc(container, verify_destinations=True):
verify_toc_destinations(container, ans)
return ans
+def ensure_id(elem):
+ if elem.tag == XHTML('a'):
+ anchor = elem.get('name', None)
+ if anchor:
+ return False, anchor
+ elem_id = elem.get('id', None)
+ if elem_id:
+ return False, elem_id
+ elem.set('id', uuid_id())
+ return True, elem.get('id')
+
+def elem_to_toc_text(elem):
+ text = xml2text(elem).strip()
+ if not text:
+ text = elem.get('title', '')
+ if not text:
+ text = elem.get('alt', '')
+ text = re.sub(r'\s+', ' ', text.strip())
+ text = text[:1000].strip()
+ if not text:
+ text = _('(Untitled)')
+ return text
+
+def from_xpaths(container, xpaths):
+ tocroot = TOC()
+ xpaths = [XPath(xp) for xp in xpaths]
+ level_prev = {i+1:None for i in xrange(len(xpaths))}
+ level_prev[0] = tocroot
+
+ for spinepath in container.spine_items:
+ name = container.abspath_to_name(spinepath)
+ root = container.parsed(name)
+ level_item_map = {i+1:frozenset(xp(root)) for i, xp in enumerate(xpaths)}
+ item_level_map = {e:i for i, elems in level_item_map.iteritems() for e in elems}
+ item_dirtied = False
+
+ for item in root.iterdescendants(etree.Element):
+ lvl = plvl = item_level_map.get(item, None)
+ if lvl is None:
+ continue
+ parent = None
+ while parent is None:
+ plvl -= 1
+ parent = level_prev[plvl]
+ lvl = plvl + 1
+ dirtied, elem_id = ensure_id(item)
+ text = elem_to_toc_text(item)
+ item_dirtied = dirtied or item_dirtied
+ toc = parent.add(text, name, elem_id)
+ toc.dest_exists = True
+ level_prev[lvl] = toc
+ for i in xrange(lvl+1, len(xpaths)+1):
+ level_prev[i] = None
+
+ if item_dirtied:
+ container.commit_item(name, keep_parsed=True)
+
+ return tocroot
+
+def from_links(container):
+ toc = TOC()
+ link_path = XPath('//h:a[@href]')
+ seen_titles, seen_dests = set(), set()
+ for spinepath in container.spine_items:
+ name = container.abspath_to_name(spinepath)
+ root = container.parsed(name)
+ for a in link_path(root):
+ href = a.get('href')
+ if not href or not href.strip():
+ continue
+ dest = container.href_to_name(href, base=name)
+ frag = href.rpartition('#')[-1] or None
+ if (dest, frag) in seen_dests:
+ continue
+ seen_dests.add((dest, frag))
+ text = elem_to_toc_text(a)
+ if text in seen_titles:
+ continue
+ seen_titles.add(text)
+ toc.add(text, dest, frag=frag)
+ verify_toc_destinations(container, toc)
+ for child in toc:
+ if not child.dest_exists:
+ toc.remove(child)
+ return toc
def add_id(container, name, loc):
root = container.parsed(name)
diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py
index 114bc3587f..3fac8c43cc 100644
--- a/src/calibre/ebooks/oeb/stylizer.py
+++ b/src/calibre/ebooks/oeb/stylizer.py
@@ -305,8 +305,17 @@ class Stylizer(object):
href = stylesheet.href
self.stylesheets.add(href)
for rule in stylesheet.cssRules:
- rules.extend(self.flatten_rule(rule, href, index))
- index = index + 1
+ if rule.type == rule.MEDIA_RULE:
+ media = {rule.media.item(i) for i in
+ xrange(rule.media.length)}
+ if not media.intersection({'all', 'screen', 'amzn-kf8'}):
+ continue
+ for subrule in rule.cssRules:
+ rules.extend(self.flatten_rule(subrule, href, index))
+ index += 1
+ else:
+ rules.extend(self.flatten_rule(rule, href, index))
+ index = index + 1
rules.sort()
self.rules = rules
self._styles = {}
diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.py b/src/calibre/gui2/catalog/catalog_epub_mobi.py
index 57859ab501..00f9c51ccd 100644
--- a/src/calibre/gui2/catalog/catalog_epub_mobi.py
+++ b/src/calibre/gui2/catalog/catalog_epub_mobi.py
@@ -11,21 +11,22 @@ import re, sys
from functools import partial
from calibre.ebooks.conversion.config import load_defaults
-from calibre.gui2 import gprefs, open_url, question_dialog
+from calibre.gui2 import gprefs, open_url, question_dialog, error_dialog
+from calibre.utils.config import JSONConfig
from calibre.utils.icu import sort_key
from catalog_epub_mobi_ui import Ui_Form
from PyQt4.Qt import (Qt, QAbstractItemView, QCheckBox, QComboBox,
- QDoubleSpinBox, QIcon, QLineEdit, QObject, QRadioButton, QSize, QSizePolicy,
- QTableWidget, QTableWidgetItem, QTextEdit, QToolButton, QUrl,
- QVBoxLayout, QWidget,
+ QDoubleSpinBox, QIcon, QInputDialog, QLineEdit, QObject, QRadioButton,
+ QSize, QSizePolicy, QTableWidget, QTableWidgetItem, QTextEdit, QToolButton,
+ QUrl, QVBoxLayout, QWidget,
SIGNAL)
class PluginWidget(QWidget,Ui_Form):
TITLE = _('E-book options')
HELP = _('Options specific to')+' AZW3/EPUB/MOBI '+_('output')
- DEBUG = False
+ DEBUG = True
# Output synced to the connected device?
sync_enabled = True
@@ -212,8 +213,8 @@ class PluginWidget(QWidget,Ui_Form):
else:
results = _truncated_results(excluded_tags)
finally:
- if self.DEBUG:
- print(results)
+ if False and self.DEBUG:
+ print("exclude_genre_changed(): %s" % results)
self.exclude_genre_results.clear()
self.exclude_genre_results.setText(results)
@@ -239,11 +240,11 @@ class PluginWidget(QWidget,Ui_Form):
Toggle Description-related controls
'''
self.header_note_source_field.setEnabled(enabled)
- self.thumb_width.setEnabled(enabled)
- self.merge_source_field.setEnabled(enabled)
- self.merge_before.setEnabled(enabled)
- self.merge_after.setEnabled(enabled)
self.include_hr.setEnabled(enabled)
+ self.merge_after.setEnabled(enabled)
+ self.merge_before.setEnabled(enabled)
+ self.merge_source_field.setEnabled(enabled)
+ self.thumb_width.setEnabled(enabled)
def generate_genres_changed(self, enabled):
'''
@@ -263,6 +264,22 @@ class PluginWidget(QWidget,Ui_Form):
self.genre_source_field_name = genre_source_spec['field']
self.exclude_genre_changed()
+ def get_format_and_title(self):
+ current_format = None
+ current_title = None
+ self.parentWidget().blockSignals(True)
+ for peer in self.parentWidget().children():
+ if peer == self:
+ continue
+ elif peer.children():
+ for child in peer.children():
+ if child.objectName() == 'format':
+ current_format = str(child.currentText()).strip()
+ elif child.objectName() == 'title':
+ current_title = str(child.text()).strip()
+ self.parentWidget().blockSignals(False)
+ return current_format, current_title
+
def header_note_source_field_changed(self,new_index):
'''
Process changes in the header_note_source_field combo box
@@ -374,15 +391,20 @@ class PluginWidget(QWidget,Ui_Form):
# Initialize exclusion rules
self.exclusion_rules_table = ExclusionRules(self.exclusion_rules_gb,
- "exclusion_rules_tw",exclusion_rules, self.eligible_custom_fields,self.db)
+ "exclusion_rules_tw", exclusion_rules, self.eligible_custom_fields, self.db)
# Initialize prefix rules
self.prefix_rules_table = PrefixRules(self.prefix_rules_gb,
- "prefix_rules_tw",prefix_rules, self.eligible_custom_fields,self.db)
+ "prefix_rules_tw", prefix_rules, self.eligible_custom_fields, self.db)
# Initialize excluded genres preview
self.exclude_genre_changed()
+ # Hook Preset signals
+ self.preset_delete_pb.clicked.connect(self.preset_remove)
+ self.preset_save_pb.clicked.connect(self.preset_save)
+ self.preset_field.currentIndexChanged[str].connect(self.preset_change)
+
def merge_source_field_changed(self,new_index):
'''
Process changes in the merge_source_field combo box
@@ -404,10 +426,12 @@ class PluginWidget(QWidget,Ui_Form):
self.include_hr.setEnabled(False)
def options(self):
- # Save/return the current options
- # exclude_genre stores literally
- # Section switches store as True/False
- # others store as lists
+ '''
+ Return, optionally save current options
+ exclude_genre stores literally
+ Section switches store as True/False
+ others store as lists
+ '''
opts_dict = {}
prefix_rules_processed = False
@@ -469,7 +493,7 @@ class PluginWidget(QWidget,Ui_Form):
except:
opts_dict['output_profile'] = ['default']
- if self.DEBUG:
+ if False and self.DEBUG:
print "opts_dict"
for opt in sorted(opts_dict.keys(), key=sort_key):
print " %s: %s" % (opt, repr(opts_dict[opt]))
@@ -544,6 +568,215 @@ class PluginWidget(QWidget,Ui_Form):
self.genre_source_fields = custom_fields
self.genre_source_field.currentIndexChanged.connect(self.genre_source_field_changed)
+ # Populate the Presets combo box
+ self.presets = JSONConfig("catalog_presets")
+ self.preset_field.addItem("")
+ self.preset_field_values = sorted([p for p in self.presets], key=sort_key)
+ self.preset_field.addItems(self.preset_field_values)
+
+ def preset_change(self, item_name):
+ '''
+ Update catalog options from current preset
+ '''
+ if not item_name:
+ return
+
+ current_preset = str(self.preset_field.currentText())
+ options = self.presets[current_preset]
+
+ exclusion_rules = []
+ prefix_rules = []
+ for opt in self.OPTION_FIELDS:
+ c_name, c_def, c_type = opt
+ if c_name == 'preset_field':
+ continue
+ # Ignore extra entries in options for cli invocation
+ if c_name in options:
+ opt_value = options[c_name]
+ else:
+ continue
+ if c_type in ['check_box']:
+ getattr(self, c_name).setChecked(eval(str(opt_value)))
+ if c_name == 'generate_genres':
+ self.genre_source_field.setEnabled(eval(str(opt_value)))
+ elif c_type in ['combo_box']:
+ if opt_value is None:
+ index = 0
+ if c_name == 'genre_source_field':
+ index = self.genre_source_field.findText(_('Tags'))
+ else:
+ index = getattr(self,c_name).findText(opt_value)
+ if index == -1:
+ if c_name == 'read_source_field':
+ index = self.read_source_field.findText(_('Tags'))
+ elif c_name == 'genre_source_field':
+ index = self.genre_source_field.findText(_('Tags'))
+ getattr(self,c_name).setCurrentIndex(index)
+ elif c_type in ['line_edit']:
+ getattr(self, c_name).setText(opt_value if opt_value else '')
+ elif c_type in ['radio_button'] and opt_value is not None:
+ getattr(self, c_name).setChecked(opt_value)
+ elif c_type in ['spin_box']:
+ getattr(self, c_name).setValue(float(opt_value))
+ if c_type == 'table_widget':
+ if c_name == 'exclusion_rules_tw':
+ if opt_value not in exclusion_rules:
+ exclusion_rules.append(opt_value)
+ if c_name == 'prefix_rules_tw':
+ if opt_value not in prefix_rules:
+ prefix_rules.append(opt_value)
+
+ # Reset exclusion rules
+ self.exclusion_rules_table.clearLayout()
+ self.exclusion_rules_table = ExclusionRules(self.exclusion_rules_gb,
+ "exclusion_rules_tw", exclusion_rules, self.eligible_custom_fields, self.db)
+
+ # Reset prefix rules
+ self.prefix_rules_table.clearLayout()
+ self.prefix_rules_table = PrefixRules(self.prefix_rules_gb,
+ "prefix_rules_tw", prefix_rules, self.eligible_custom_fields, self.db)
+
+ # Reset excluded genres preview
+ self.exclude_genre_changed()
+
+ # Reset format and title
+ format = options['format']
+ title = options['catalog_title']
+ self.set_format_and_title(format, title)
+
+ # Reset Descriptions-related enable/disable switches
+ self.generate_descriptions_changed(self.generate_descriptions.isChecked())
+
+ def preset_remove(self):
+ if self.preset_field.currentIndex() == 0:
+ return
+
+ if not question_dialog(self, _("Delete saved catalog preset"),
+ _("The selected saved catalog preset will be deleted. "
+ "Are you sure?")):
+ return
+
+ item_id = self.preset_field.currentIndex()
+ item_name = unicode(self.preset_field.currentText())
+
+ self.preset_field.blockSignals(True)
+ self.preset_field.removeItem(item_id)
+ self.preset_field.blockSignals(False)
+ self.preset_field.setCurrentIndex(0)
+
+ if item_name in self.presets.keys():
+ del(self.presets[item_name])
+ self.presets.commit()
+
+ def preset_save(self):
+ names = ['']
+ names.extend(self.preset_field_values)
+ try:
+ dex = names.index(self.preset_search_name)
+ except:
+ dex = 0
+ name = ''
+ while not name:
+ name, ok = QInputDialog.getItem(self, _('Save catalog preset'),
+ _('Preset name:'), names, dex, True)
+ if not ok:
+ return
+ if not name:
+ error_dialog(self, _("Save catalog preset"),
+ _("You must provide a name."), show=True)
+ new = True
+ name = unicode(name)
+ if name in self.presets.keys():
+ if not question_dialog(self, _("Save catalog preset"),
+ _("That saved preset already exists and will be overwritten. "
+ "Are you sure?")):
+ return
+ new = False
+
+ preset = {}
+ prefix_rules_processed = False
+ exclusion_rules_processed = False
+
+ for opt in self.OPTION_FIELDS:
+ c_name, c_def, c_type = opt
+ if c_name == 'exclusion_rules_tw' and exclusion_rules_processed:
+ continue
+ if c_name == 'prefix_rules_tw' and prefix_rules_processed:
+ continue
+
+ if c_type in ['check_box', 'radio_button']:
+ opt_value = getattr(self, c_name).isChecked()
+ elif c_type in ['combo_box']:
+ if c_name == 'preset_field':
+ continue
+ opt_value = unicode(getattr(self,c_name).currentText()).strip()
+ elif c_type in ['line_edit']:
+ opt_value = unicode(getattr(self, c_name).text()).strip()
+ elif c_type in ['spin_box']:
+ opt_value = unicode(getattr(self, c_name).value())
+ elif c_type in ['table_widget']:
+ if c_name == 'prefix_rules_tw':
+ opt_value = self.prefix_rules_table.get_data()
+ prefix_rules_processed = True
+ if c_name == 'exclusion_rules_tw':
+ opt_value = self.exclusion_rules_table.get_data()
+ exclusion_rules_processed = True
+
+ preset[c_name] = opt_value
+ # Construct cli version of table rules
+ if c_name in ['exclusion_rules_tw','prefix_rules_tw']:
+ self.construct_tw_opts_object(c_name, opt_value, preset)
+
+ format, title = self.get_format_and_title()
+ preset['format'] = format
+ preset['catalog_title'] = title
+
+ # Additional items needed for cli invocation
+ # Generate specs for merge_comments, header_note_source_field, genre_source_field
+ checked = ''
+ if self.merge_before.isChecked():
+ checked = 'before'
+ elif self.merge_after.isChecked():
+ checked = 'after'
+ include_hr = self.include_hr.isChecked()
+ preset['merge_comments_rule'] = "%s:%s:%s" % \
+ (self.merge_source_field_name, checked, include_hr)
+
+ preset['header_note_source_field'] = unicode(self.header_note_source_field.currentText())
+ preset['genre_source_field'] = unicode(self.genre_source_field.currentText())
+
+ # Append the current output profile
+ try:
+ preset['output_profile'] = load_defaults('page_setup')['output_profile']
+ except:
+ preset['output_profile'] = 'default'
+
+ self.presets[name] = preset
+ self.presets.commit()
+
+ if new:
+ self.preset_field.blockSignals(True)
+ self.preset_field.clear()
+ self.preset_field.addItem('')
+ self.preset_field_values = sorted([q for q in self.presets], key=sort_key)
+ self.preset_field.addItems(self.preset_field_values)
+ self.preset_field.blockSignals(False)
+ self.preset_field.setCurrentIndex(self.preset_field.findText(name))
+
+ def set_format_and_title(self, format, title):
+ for peer in self.parentWidget().children():
+ if peer == self:
+ continue
+ elif peer.children():
+ for child in peer.children():
+ if child.objectName() == 'format':
+ index = child.findText(format)
+ child.blockSignals(True)
+ child.setCurrentIndex(index)
+ child.blockSignals(False)
+ elif child.objectName() == 'title':
+ child.setText(title)
+
def show_help(self):
'''
Display help file
@@ -631,6 +864,7 @@ class GenericRulesTable(QTableWidget):
self.last_row_selected = self.currentRow()
self.last_rows_selected = self.selectionModel().selectedRows()
+ # Add the controls
self._init_controls()
# Hook check_box changes
@@ -681,6 +915,21 @@ class GenericRulesTable(QTableWidget):
# In case table was empty
self.horizontalHeader().setStretchLastSection(True)
+ def clearLayout(self):
+ if self.layout is not None:
+ old_layout = self.layout
+
+ for child in old_layout.children():
+ for i in reversed(range(child.count())):
+ if child.itemAt(i).widget() is not None:
+ child.itemAt(i).widget().setParent(None)
+ import sip
+ sip.delete(child)
+
+ for i in reversed(range(old_layout.count())):
+ if old_layout.itemAt(i).widget() is not None:
+ old_layout.itemAt(i).widget().setParent(None)
+
def delete_row(self):
if self.DEBUG:
print("%s:delete_row()" % self.objectName())
diff --git a/src/calibre/gui2/catalog/catalog_epub_mobi.ui b/src/calibre/gui2/catalog/catalog_epub_mobi.ui
index d212b0aa6f..608c5c81aa 100644
--- a/src/calibre/gui2/catalog/catalog_epub_mobi.ui
+++ b/src/calibre/gui2/catalog/catalog_epub_mobi.ui
@@ -20,6 +20,54 @@
Form
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Presets
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Select catalog preset to load
+
+
+
+ -
+
+
+ Save current catalog settings as preset
+
+
+ Save
+
+
+
+ -
+
+
+ Delete current preset
+
+
+ Delete
+
+
+
+
+
+
-
@@ -46,6 +94,9 @@
true
+
+ List of books, sorted by Author
+
&Authors
@@ -54,15 +105,21 @@
- -
+
-
+
+ List of books, sorted by Title
+
&Titles
- -
+
-
+
+ List of series books, sorted by Series
+
&Series
@@ -72,6 +129,9 @@
-
+
+ List of books, sorted by Genre
+
&Genres
@@ -80,13 +140,13 @@
-
- Field containing Genre information
+ Field containing Genres
- -
+
-
-
@@ -96,6 +156,9 @@
26
+
+ List of books, sorted by date added to calibre
+
&Recently Added
@@ -103,7 +166,7 @@
- -
+
-
-
@@ -113,6 +176,9 @@
26
+
+ Individual descriptions of books with cover thumbs, sorted by author
+
&Descriptions
@@ -120,6 +186,41 @@
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
@@ -347,7 +448,7 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
- Custom column containing additional content to be merged with Comments metadata.
+ Custom column containing additional content to be merged with Comments metadata in Descriptions section.
@@ -361,7 +462,7 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
-
- Merge additional content before Comments metadata.
+ Merge additional content before Comments in Descriptions section.
&Before
@@ -374,7 +475,7 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
-
- Merge additional content after Comments metadata.
+ Merge additional content after Comments in Descriptions section.
&After
@@ -394,7 +495,7 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
-
- Separate Comments metadata and additional content with a horizontal rule.
+ Separate Comments metadata and additional content with a horizontal rule in Descriptions section.
Include &Separator
@@ -514,7 +615,7 @@ The default pattern \[.+\]|\+ excludes tags of the form [tag], e.g., [Test book]
- Custom column source for text to include in Description section.
+ Custom column source for text to include in Descriptions section.
diff --git a/src/calibre/gui2/preferences/coloring.py b/src/calibre/gui2/preferences/coloring.py
index 1dce44b865..8d27d14e5b 100644
--- a/src/calibre/gui2/preferences/coloring.py
+++ b/src/calibre/gui2/preferences/coloring.py
@@ -763,22 +763,24 @@ class EditRules(QWidget): # {{{
' double clicking it.'))
self.add_advanced_button.setVisible(False)
- def _add_rule(self, dlg):
- if dlg.exec_() == dlg.Accepted:
- kind, col, r = dlg.rule
+ def add_rule(self):
+ d = RuleEditor(self.model.fm, self.pref_name)
+ d.add_blank_condition()
+ if d.exec_() == d.Accepted:
+ kind, col, r = d.rule
if kind and r and col:
idx = self.model.add_rule(kind, col, r)
self.rules_view.scrollTo(idx)
self.changed.emit()
- def add_rule(self):
- d = RuleEditor(self.model.fm, self.pref_name)
- d.add_blank_condition()
- self._add_rule(d)
-
def add_advanced(self):
td = TemplateDialog(self, '', mi=self.mi, fm=self.fm, color_field='')
- self._add_rule(('color', td[0], td[1]))
+ if td.exec_() == td.Accepted:
+ col, r = td.rule
+ if r and col:
+ idx = self.model.add_rule('color', col, r)
+ self.rules_view.scrollTo(idx)
+ self.changed.emit()
def edit_rule(self, index):
try:
diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py
index 63aad654ad..06ddc37eaa 100644
--- a/src/calibre/gui2/toc/main.py
+++ b/src/calibre/gui2/toc/main.py
@@ -7,7 +7,7 @@ __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import sys, os
+import sys, os, textwrap
from threading import Thread
from functools import partial
@@ -17,20 +17,65 @@ from PyQt4.Qt import (QPushButton, QFrame, QVariant,
QToolButton, QItemSelectionModel)
from calibre.ebooks.oeb.polish.container import get_container, AZW3Container
-from calibre.ebooks.oeb.polish.toc import get_toc, add_id, TOC, commit_toc
+from calibre.ebooks.oeb.polish.toc import (
+ get_toc, add_id, TOC, commit_toc, from_xpaths, from_links)
from calibre.gui2 import Application, error_dialog, gprefs
from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.gui2.toc.location import ItemEdit
+from calibre.gui2.convert.xpath_wizard import XPathEdit
from calibre.utils.logging import GUILog
ICON_SIZE = 24
+class XPathDialog(QDialog): # {{{
+
+ def __init__(self, parent):
+ QDialog.__init__(self, parent)
+ self.setWindowTitle(_('Create ToC from XPath'))
+ self.l = l = QVBoxLayout()
+ self.setLayout(l)
+ self.la = la = QLabel(_(
+ 'Specify a series of XPath expressions for the different levels of'
+ ' the Table of Contents. You can use the wizard buttons to help'
+ ' you create XPath expressions.'))
+ la.setWordWrap(True)
+ l.addWidget(la)
+ self.widgets = []
+ for i in xrange(5):
+ la = _('Level %s ToC:')%('&%d'%(i+1))
+ xp = XPathEdit(self)
+ xp.set_msg(la)
+ self.widgets.append(xp)
+ l.addWidget(xp)
+
+ self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
+ bb.accepted.connect(self.accept)
+ bb.rejected.connect(self.reject)
+ l.addStretch()
+ l.addWidget(bb)
+ self.resize(self.sizeHint() + QSize(50, 75))
+
+ def accept(self):
+ for w in self.widgets:
+ if not w.check():
+ return error_dialog(self, _('Invalid XPath'),
+ _('The XPath expression %s is not valid.')%w.xpath,
+ show=True)
+ super(XPathDialog, self).accept()
+
+ @property
+ def xpaths(self):
+ return [w.xpath for w in self.widgets if w.xpath.strip()]
+# }}}
+
class ItemView(QFrame): # {{{
add_new_item = pyqtSignal(object, object)
delete_item = pyqtSignal()
flatten_item = pyqtSignal()
go_to_root = pyqtSignal()
+ create_from_xpath = pyqtSignal(object)
+ create_from_links = pyqtSignal()
def __init__(self, parent):
QFrame.__init__(self, parent)
@@ -60,6 +105,41 @@ class ItemView(QFrame): # {{{
self.add_new_to_root_button = b = QPushButton(_('Create a &new entry'))
b.clicked.connect(self.add_new_to_root)
l.addWidget(b)
+ l.addStretch()
+
+ self.cfmhb = b = QPushButton(_('Generate ToC from &major headings'))
+ b.clicked.connect(self.create_from_major_headings)
+ b.setToolTip(textwrap.fill(_(
+ 'Generate a Table of Contents from the major headings in the book.'
+ ' This will work if the book identifies its headings using HTML'
+ ' heading tags. Uses the , and tags.')))
+ l.addWidget(b)
+ self.cfmab = b = QPushButton(_('Generate ToC from &all headings'))
+ b.clicked.connect(self.create_from_all_headings)
+ b.setToolTip(textwrap.fill(_(
+ 'Generate a Table of Contents from all the headings in the book.'
+ ' This will work if the book identifies its headings using HTML'
+ ' heading tags. Uses the tags.')))
+ l.addWidget(b)
+
+ self.lb = b = QPushButton(_('Generate ToC from &links'))
+ b.clicked.connect(self.create_from_links)
+ b.setToolTip(textwrap.fill(_(
+ 'Generate a Table of Contents from all the links in the book.'
+ ' Links that point to destinations that do not exist in the book are'
+ ' ignored. Also multiple links with the same destination or the same'
+ ' text are ignored.'
+ )))
+ l.addWidget(b)
+
+ self.xpb = b = QPushButton(_('Generate ToC from &XPath'))
+ b.clicked.connect(self.create_from_user_xpath)
+ b.setToolTip(textwrap.fill(_(
+ 'Generate a Table of Contents from arbitrary XPath expressions.'
+ )))
+ l.addWidget(b)
+
+
l.addStretch()
self.w1 = la = QLabel(_('WARNING: calibre only supports the '
'creation of linear ToCs in AZW3 files. In a '
@@ -121,19 +201,21 @@ class ItemView(QFrame): # {{{
ip.b5 = b = QPushButton(QIcon(I('plus.png')), _('New entry &below this entry'))
b.clicked.connect(partial(self.add_new, 'after'))
l.addWidget(b, l.rowCount(), 0, 1, 2)
- ip.hl4 = hl = QFrame()
- hl.setFrameShape(hl.HLine)
- l.addWidget(hl, l.rowCount(), 0, 1, 2)
- l.setRowMinimumHeight(rs, 20)
-
# Flatten entry
- rs = l.rowCount()
ip.b3 = b = QPushButton(QIcon(I('heuristics.png')), _('&Flatten this entry'))
b.clicked.connect(self.flatten_item)
b.setToolTip(_('All children of this entry are brought to the same '
'level as this entry.'))
l.addWidget(b, l.rowCount()+1, 0, 1, 2)
- ip.b4 = b = QPushButton(QIcon(I('back.png')), _('&Return to root'))
+
+ ip.hl4 = hl = QFrame()
+ hl.setFrameShape(hl.HLine)
+ l.addWidget(hl, l.rowCount(), 0, 1, 2)
+ l.setRowMinimumHeight(rs, 20)
+
+ # Return to welcome
+ rs = l.rowCount()
+ ip.b4 = b = QPushButton(QIcon(I('back.png')), _('&Return to welcome screen'))
b.clicked.connect(self.go_to_root)
b.setToolTip(_('Go back to the top level view'))
l.addWidget(b, l.rowCount()+1, 0, 1, 2)
@@ -147,6 +229,17 @@ class ItemView(QFrame): # {{{
self.w2.setWordWrap(True)
l.addWidget(la, l.rowCount(), 0, 1, 2)
+ def create_from_major_headings(self):
+ self.create_from_xpath.emit(['//h:h%d'%i for i in xrange(1, 4)])
+
+ def create_from_all_headings(self):
+ self.create_from_xpath.emit(['//h:h%d'%i for i in xrange(1, 7)])
+
+ def create_from_user_xpath(self):
+ d = XPathDialog(self)
+ if d.exec_() == d.Accepted and d.xpaths:
+ self.create_from_xpath.emit(d.xpaths)
+
def hide_azw3_warning(self):
self.w1.setVisible(False), self.w2.setVisible(False)
@@ -242,6 +335,8 @@ class TOCView(QWidget): # {{{
self.item_view = i = ItemView(self)
self.item_view.delete_item.connect(self.delete_current_item)
i.add_new_item.connect(self.add_new_item)
+ i.create_from_xpath.connect(self.create_from_xpath)
+ i.create_from_links.connect(self.create_from_links)
i.flatten_item.connect(self.flatten_item)
i.go_to_root.connect(self.go_to_root)
l.addWidget(i, 0, 4, col, 1)
@@ -443,6 +538,32 @@ class TOCView(QWidget): # {{{
process_node(self.tocw.invisibleRootItem(), root)
return root
+ def insert_toc_fragment(self, toc):
+
+ def process_node(root, tocparent, added):
+ for child in tocparent:
+ item = self.create_item(root, child)
+ added.append(item)
+ process_node(item, child, added)
+
+ nodes = []
+ process_node(self.root, toc, nodes)
+ self.highlight_item(nodes[0])
+
+ def create_from_xpath(self, xpaths):
+ toc = from_xpaths(self.ebook, xpaths)
+ if len(toc) == 0:
+ return error_dialog(self, _('No items found'),
+ _('No items were found that could be added to the Table of Contents.'), show=True)
+ self.insert_toc_fragment(toc)
+
+ def create_from_links(self):
+ toc = from_links(self.ebook)
+ if len(toc) == 0:
+ return error_dialog(self, _('No items found'),
+ _('No links were found that could be added to the Table of Contents.'), show=True)
+ self.insert_toc_fragment(toc)
+
# }}}
class TOCEditor(QDialog): # {{{
diff --git a/src/calibre/library/catalogs/epub_mobi.py b/src/calibre/library/catalogs/epub_mobi.py
index 96290601cd..d7c24f8a97 100644
--- a/src/calibre/library/catalogs/epub_mobi.py
+++ b/src/calibre/library/catalogs/epub_mobi.py
@@ -7,7 +7,7 @@ __license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import os
+import datetime, os, time
from collections import namedtuple
from calibre import strftime
@@ -17,11 +17,11 @@ from calibre.ebooks import calibre_cover
from calibre.library import current_library_name
from calibre.library.catalogs import AuthorSortMismatchException, EmptyCatalogException
from calibre.ptempfile import PersistentTemporaryFile
+from calibre.utils.config import JSONConfig
from calibre.utils.localization import calibre_langcode_to_name, canonicalize_lang, get_lang
Option = namedtuple('Option', 'option, default, dest, action, help')
-
class EPUB_MOBI(CatalogPlugin):
'ePub catalog generator'
@@ -162,6 +162,14 @@ class EPUB_MOBI(CatalogPlugin):
"When multiple rules are defined, the first matching rule will be used.\n"
"Default:\n" + '"' + '%default' + '"' + "\n"
"Applies to AZW3, ePub, MOBI output formats")),
+ Option('--preset',
+ default=None,
+ dest='preset',
+ action=None,
+ help=_("Use a named preset created with the GUI Catalog builder.\n"
+ "A preset specifies all settings for building a catalog.\n"
+ "Default: '%default'\n"
+ "Applies to AZW3, ePub, MOBI output formats")),
Option('--use-existing-cover',
default=False,
dest='use_existing_cover',
@@ -184,6 +192,43 @@ class EPUB_MOBI(CatalogPlugin):
from calibre.library.catalogs.epub_mobi_builder import CatalogBuilder
from calibre.utils.logging import default_log as log
+ # If preset specified from the cli, insert stored options from JSON file
+ if hasattr(opts, 'preset') and opts.preset:
+ available_presets = JSONConfig("catalog_presets")
+ if not opts.preset in available_presets:
+ if available_presets:
+ print(_('Error: Preset "%s" not found.' % opts.preset))
+ print(_('Stored presets: %s' % ', '.join([p for p in sorted(available_presets.keys())])))
+ else:
+ print(_('Error: No stored presets.'))
+ return 1
+
+ # Copy the relevant preset values to the opts object
+ for item in available_presets[opts.preset]:
+ if not item in ['exclusion_rules_tw', 'format', 'prefix_rules_tw']:
+ setattr(opts, item, available_presets[opts.preset][item])
+
+ # Provide an unconnected device
+ opts.connected_device = {
+ 'is_device_connected': False,
+ 'kind': None,
+ 'name': None,
+ 'save_template': None,
+ 'serial': None,
+ 'storage': None,
+ }
+
+ # Convert prefix_rules and exclusion_rules from JSON lists to tuples
+ prs = []
+ for rule in opts.prefix_rules:
+ prs.append(tuple(rule))
+ opts.prefix_rules = tuple(prs)
+
+ ers = []
+ for rule in opts.exclusion_rules:
+ ers.append(tuple(rule))
+ opts.exclusion_rules = tuple(ers)
+
opts.log = log
opts.fmt = self.fmt = path_to_output.rpartition('.')[2]
@@ -329,32 +374,37 @@ class EPUB_MOBI(CatalogPlugin):
log.error("incorrect number of args for --exclusion-rules: %s" % repr(rule))
# Display opts
- keys = opts_dict.keys()
- keys.sort()
+ keys = sorted(opts_dict.keys())
build_log.append(" opts:")
for key in keys:
if key in ['catalog_title', 'author_clip', 'connected_kindle', 'creator',
'cross_reference_authors', 'description_clip', 'exclude_book_marker',
'exclude_genre', 'exclude_tags', 'exclusion_rules', 'fmt',
'genre_source_field', 'header_note_source_field', 'merge_comments_rule',
- 'output_profile', 'prefix_rules', 'read_book_marker',
+ 'output_profile', 'prefix_rules', 'preset', 'read_book_marker',
'search_text', 'sort_by', 'sort_descriptions_by_author', 'sync',
'thumb_width', 'use_existing_cover', 'wishlist_tag']:
build_log.append(" %s: %s" % (key, repr(opts_dict[key])))
if opts.verbose:
log('\n'.join(line for line in build_log))
+
+ # Capture start_time
+ opts.start_time = time.time()
+
self.opts = opts
+ if opts.verbose:
+ log.info(" Begin catalog source generation (%s)" %
+ str(datetime.timedelta(seconds = int(time.time() - opts.start_time))))
+
# Launch the Catalog builder
catalog = CatalogBuilder(db, opts, self, report_progress=notification)
- if opts.verbose:
- log.info(" Begin catalog source generation")
-
try:
catalog.build_sources()
if opts.verbose:
- log.info(" Completed catalog source generation\n")
+ log.info(" Completed catalog source generation (%s)\n" %
+ str(datetime.timedelta(seconds = int(time.time() - opts.start_time))))
except (AuthorSortMismatchException, EmptyCatalogException), e:
log.error(" *** Terminated catalog generation: %s ***" % e)
except:
@@ -444,5 +494,9 @@ class EPUB_MOBI(CatalogPlugin):
os.remove(epub_shell)
zip_rebuilder(input_path, os.path.join(catalog_debug_path, 'input.epub'))
+ if opts.verbose:
+ log.info(" Catalog creation complete (%s)\n" %
+ str(datetime.timedelta(seconds = int(time.time() - opts.start_time))))
+
# returns to gui2.actions.catalog:catalog_generated()
return catalog.error
diff --git a/src/calibre/library/catalogs/epub_mobi_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py
index 9f946e2ee0..04116cb0e3 100644
--- a/src/calibre/library/catalogs/epub_mobi_builder.py
+++ b/src/calibre/library/catalogs/epub_mobi_builder.py
@@ -3,7 +3,7 @@
__license__ = 'GPL v3'
__copyright__ = '2010, Greg Riker'
-import datetime, htmlentitydefs, os, platform, re, shutil, unicodedata, zlib
+import datetime, htmlentitydefs, os, platform, re, shutil, time, unicodedata, zlib
from copy import deepcopy
from xml.sax.saxutils import escape
@@ -1277,7 +1277,6 @@ class CatalogBuilder(object):
self.opts.log.info('%s' % _format_tag_list(genre_tags_dict, header="enabled genres"))
self.opts.log.info('%s' % _format_tag_list(excluded_tags, header="excluded genres"))
- print("genre_tags_dict: %s" % genre_tags_dict)
return genre_tags_dict
def filter_excluded_genres(self, tags, regex):
@@ -4856,7 +4855,13 @@ class CatalogBuilder(object):
self.progress_int = 0.01
self.reporter(self.progress_int, self.progress_string)
if self.opts.cli_environment:
- self.opts.log(u"%3.0f%% %s" % (self.progress_int * 100, self.progress_string))
+ log_msg = u"%3.0f%% %s" % (self.progress_int * 100, self.progress_string)
+ if self.opts.verbose:
+ log_msg += " (%s)" % str(datetime.timedelta(seconds=int(time.time() - self.opts.start_time)))
+ else:
+ log_msg = ("%s (%s)" % (self.progress_string,
+ str(datetime.timedelta(seconds=int(time.time() - self.opts.start_time)))))
+ self.opts.log(log_msg)
def update_progress_micro_step(self, description, micro_step_pct):
""" Update calibre's job status UI.
diff --git a/src/calibre/utils/imghdr.py b/src/calibre/utils/imghdr.py
new file mode 100644
index 0000000000..3bd515bac5
--- /dev/null
+++ b/src/calibre/utils/imghdr.py
@@ -0,0 +1,156 @@
+"""Recognize image file formats based on their first few bytes."""
+
+__all__ = ["what"]
+
+#-------------------------#
+# Recognize image headers #
+#-------------------------#
+
+def what(file, h=None):
+ if h is None:
+ if isinstance(file, basestring):
+ f = open(file, 'rb')
+ h = f.read(32)
+ else:
+ location = file.tell()
+ h = file.read(32)
+ file.seek(location)
+ f = None
+ else:
+ f = None
+ try:
+ for tf in tests:
+ res = tf(h, f)
+ if res:
+ return res
+ finally:
+ if f: f.close()
+ return None
+
+
+#---------------------------------#
+# Subroutines per image file type #
+#---------------------------------#
+
+tests = []
+
+def test_jpeg(h, f):
+ """JPEG data in JFIF format (Changed by Kovid to mimic the file utility,
+ the original code was failing with some jpegs that included ICC_PROFILE
+ data, for example: http://nationalpostnews.files.wordpress.com/2013/03/budget.jpeg?w=300&h=1571)"""
+ if (h[6:10] in (b'JFIF', b'Exif')) or (h[:2] == b'\xff\xd8' and b'JFIF' in h[:32]):
+ return 'jpeg'
+
+tests.append(test_jpeg)
+
+def test_png(h, f):
+ if h[:8] == "\211PNG\r\n\032\n":
+ return 'png'
+
+tests.append(test_png)
+
+def test_gif(h, f):
+ """GIF ('87 and '89 variants)"""
+ if h[:6] in ('GIF87a', 'GIF89a'):
+ return 'gif'
+
+tests.append(test_gif)
+
+def test_tiff(h, f):
+ """TIFF (can be in Motorola or Intel byte order)"""
+ if h[:2] in ('MM', 'II'):
+ return 'tiff'
+
+tests.append(test_tiff)
+
+def test_rgb(h, f):
+ """SGI image library"""
+ if h[:2] == '\001\332':
+ return 'rgb'
+
+tests.append(test_rgb)
+
+def test_pbm(h, f):
+ """PBM (portable bitmap)"""
+ if len(h) >= 3 and \
+ h[0] == 'P' and h[1] in '14' and h[2] in ' \t\n\r':
+ return 'pbm'
+
+tests.append(test_pbm)
+
+def test_pgm(h, f):
+ """PGM (portable graymap)"""
+ if len(h) >= 3 and \
+ h[0] == 'P' and h[1] in '25' and h[2] in ' \t\n\r':
+ return 'pgm'
+
+tests.append(test_pgm)
+
+def test_ppm(h, f):
+ """PPM (portable pixmap)"""
+ if len(h) >= 3 and \
+ h[0] == 'P' and h[1] in '36' and h[2] in ' \t\n\r':
+ return 'ppm'
+
+tests.append(test_ppm)
+
+def test_rast(h, f):
+ """Sun raster file"""
+ if h[:4] == '\x59\xA6\x6A\x95':
+ return 'rast'
+
+tests.append(test_rast)
+
+def test_xbm(h, f):
+ """X bitmap (X10 or X11)"""
+ s = '#define '
+ if h[:len(s)] == s:
+ return 'xbm'
+
+tests.append(test_xbm)
+
+def test_bmp(h, f):
+ if h[:2] == 'BM':
+ return 'bmp'
+
+tests.append(test_bmp)
+
+#--------------------#
+# Small test program #
+#--------------------#
+
+def test():
+ import sys
+ recursive = 0
+ if sys.argv[1:] and sys.argv[1] == '-r':
+ del sys.argv[1:2]
+ recursive = 1
+ try:
+ if sys.argv[1:]:
+ testall(sys.argv[1:], recursive, 1)
+ else:
+ testall(['.'], recursive, 1)
+ except KeyboardInterrupt:
+ sys.stderr.write('\n[Interrupted]\n')
+ sys.exit(1)
+
+def testall(list, recursive, toplevel):
+ import sys
+ import os
+ for filename in list:
+ if os.path.isdir(filename):
+ print filename + '/:',
+ if recursive or toplevel:
+ print 'recursing down:'
+ import glob
+ names = glob.glob(os.path.join(filename, '*'))
+ testall(names, recursive, 0)
+ else:
+ print '*** directory (use -r) ***'
+ else:
+ print filename + ':',
+ sys.stdout.flush()
+ try:
+ print what(filename)
+ except IOError:
+ print '*** not found ***'
diff --git a/src/calibre/web/feeds/__init__.py b/src/calibre/web/feeds/__init__.py
index 43b404367c..d819aade7a 100644
--- a/src/calibre/web/feeds/__init__.py
+++ b/src/calibre/web/feeds/__init__.py
@@ -184,7 +184,12 @@ class Feed(object):
id = 'internal id#%s'%self.id_counter
if id in self.added_articles:
return
- published = item.get('date_parsed', time.gmtime())
+ published = None
+ for date_field in ('date_parsed', 'published_parsed',
+ 'updated_parsed'):
+ published = item.get(date_field, None)
+ if published is not None:
+ break
if not published:
published = time.gmtime()
self.added_articles.append(id)
diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py
index e9348f6ae7..0e0afe2bc4 100644
--- a/src/calibre/web/feeds/news.py
+++ b/src/calibre/web/feeds/news.py
@@ -355,6 +355,10 @@ class BasicNewsRecipe(Recipe):
#: The minimum jpeg quality will be 5/100 so it is possible this constraint
#: will not be met. This parameter can be overridden by the parameter
#: compress_news_images_max_size which provides a fixed maximum size for images.
+ #: Note that if you enable scale_news_images_to_device then the image will
+ #: first be scaled and then its quality lowered until its size is less than
+ #: (w * h)/factor where w and h are now the *scaled* image dimensions. In
+ #: other words, this compression happens after scaling.
compress_news_images_auto_size = 16
#: Set jpeg quality so images do not exceed the size given (in KBytes).
diff --git a/src/calibre/web/fetch/simple.py b/src/calibre/web/fetch/simple.py
index 7cc8bd9309..95b8cf0253 100644
--- a/src/calibre/web/fetch/simple.py
+++ b/src/calibre/web/fetch/simple.py
@@ -7,7 +7,7 @@ __copyright__ = '2008, Kovid Goyal '
Fetch a webpage and its links recursively. The webpages are saved to disk in
UTF-8 encoding with any charset declarations removed.
'''
-import sys, socket, os, urlparse, re, time, copy, urllib2, threading, traceback, imghdr
+import sys, socket, os, urlparse, re, time, copy, urllib2, threading, traceback
from urllib import url2pathname, quote
from httplib import responses
from base64 import b64decode
@@ -21,6 +21,7 @@ from calibre.utils.config import OptionParser
from calibre.utils.logging import Log
from calibre.utils.magick import Image
from calibre.utils.magick.draw import identify_data, thumbnail
+from calibre.utils.imghdr import what
class FetchError(Exception):
pass
@@ -413,7 +414,7 @@ class RecursiveFetcher(object):
fname = ascii_filename('img'+str(c))
if isinstance(fname, unicode):
fname = fname.encode('ascii', 'replace')
- itype = imghdr.what(None, data)
+ itype = what(None, data)
if itype is None and b'