From 44395ff195873d3036a423200d0a9e36895cbcc8 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 6 Mar 2011 03:01:08 +0100 Subject: [PATCH] imdbPy update --- libs/imdb/__init__.py | 7 +- libs/imdb/_exceptions.py | 2 +- libs/imdb/locale/generatepot.py | 0 libs/imdb/locale/imdbpy-en.po | 1257 +++++++++++++++++++ libs/imdb/locale/imdbpy-it.po | 1300 +++++++++++++++++++ libs/imdb/locale/imdbpy-tr.po | 1300 +++++++++++++++++++ libs/imdb/locale/imdbpy.pot | 1301 +++++++++++++++++++ libs/imdb/locale/rebuildmo.py | 0 libs/imdb/parser/http/__init__.py | 4 + libs/imdb/parser/http/movieParser.py | 20 +- libs/imdb/parser/http/personParser.py | 122 +- libs/imdb/parser/http/utils.py | 44 +- libs/imdb/parser/mobile/__init__.py | 64 +- libs/imdb/parser/sql/__init__.py | 1589 ++++++++++++++++++++++++ libs/imdb/parser/sql/alchemyadapter.py | 508 ++++++++ libs/imdb/parser/sql/cutils.c | 269 ++++ libs/imdb/parser/sql/dbschema.py | 461 +++++++ libs/imdb/parser/sql/objectadapter.py | 203 +++ libs/imdb/utils.py | 9 + 19 files changed, 8393 insertions(+), 67 deletions(-) mode change 100644 => 100755 libs/imdb/locale/generatepot.py create mode 100644 libs/imdb/locale/imdbpy-en.po create mode 100644 libs/imdb/locale/imdbpy-it.po create mode 100644 libs/imdb/locale/imdbpy-tr.po create mode 100644 libs/imdb/locale/imdbpy.pot mode change 100644 => 100755 libs/imdb/locale/rebuildmo.py create mode 100644 libs/imdb/parser/sql/__init__.py create mode 100644 libs/imdb/parser/sql/alchemyadapter.py create mode 100644 libs/imdb/parser/sql/cutils.c create mode 100644 libs/imdb/parser/sql/dbschema.py create mode 100644 libs/imdb/parser/sql/objectadapter.py diff --git a/libs/imdb/__init__.py b/libs/imdb/__init__.py index 4e6ac778..faaa7d38 100644 --- a/libs/imdb/__init__.py +++ b/libs/imdb/__init__.py @@ -25,7 +25,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA __all__ = ['IMDb', 'IMDbError', 'Movie', 'Person', 'Character', 'Company', 'available_access_systems'] -__version__ = VERSION = '4.6' +__version__ = VERSION = '4.8dev20110303' # Import compatibility module (importing it is enough). import _compat @@ -156,6 +156,7 @@ def IMDb(accessSystem=None, *arguments, **keywords): kwds.update(keywords) keywords = kwds except Exception, e: + import logging logging.getLogger('imdbpy').warn('Unable to read configuration' \ ' file; complete error: %s' % e) # It just LOOKS LIKE a bad habit: we tried to read config @@ -179,6 +180,10 @@ def IMDb(accessSystem=None, *arguments, **keywords): from parser.http import IMDbHTTPAccessSystem return IMDbHTTPAccessSystem(*arguments, **keywords) elif accessSystem in ('httpThin', 'webThin', 'htmlThin'): + import logging + logging.warn('httpThin is badly broken and' \ + ' will not be fixed; please switch' \ + ' to "http" or "mobile"') from parser.http import IMDbHTTPAccessSystem return IMDbHTTPAccessSystem(isThin=1, *arguments, **keywords) elif accessSystem in ('mobile',): diff --git a/libs/imdb/_exceptions.py b/libs/imdb/_exceptions.py index bfb9688a..436d01a7 100644 --- a/libs/imdb/_exceptions.py +++ b/libs/imdb/_exceptions.py @@ -23,7 +23,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import logging -class IMDbError(Exception): +class IMDbError(Exception, object): """Base class for every exception raised by the imdb package.""" _logger = logging.getLogger('imdbpy') diff --git a/libs/imdb/locale/generatepot.py b/libs/imdb/locale/generatepot.py old mode 100644 new mode 100755 diff --git a/libs/imdb/locale/imdbpy-en.po b/libs/imdb/locale/imdbpy-en.po new file mode 100644 index 00000000..3b3013c3 --- /dev/null +++ b/libs/imdb/locale/imdbpy-en.po @@ -0,0 +1,1257 @@ +# Gettext message file for imdbpy +msgid "" +msgstr "" +"Project-Id-Version: imdbpy\n" +"POT-Creation-Date: 2009-04-16 14:27+0000\n" +"PO-Revision-Date: YYYY-MM-DD HH:MM+0000\n" +"Last-Translator: YOUR NAME \n" +"Language-Team: TEAM NAME \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Language-Code: en\n" +"Language-Name: English\n" +"Preferred-Encodings: utf-8\n" +"Domain: imdbpy\n" + +# Default: Actor +msgid "actor" +msgstr "Actor" + +# Default: Actress +msgid "actress" +msgstr "Actress" + +# Default: Adaption +msgid "adaption" +msgstr "Adaption" + +# Default: Additional information +msgid "additional-information" +msgstr "Additional information" + +# Default: Admissions +msgid "admissions" +msgstr "Admissions" + +# Default: Agent address +msgid "agent-address" +msgstr "Agent address" + +# Default: Airing +msgid "airing" +msgstr "Airing" + +# Default: Akas +msgid "akas" +msgstr "Akas" + +# Default: All products +msgid "all-products" +msgstr "All products" + +# Default: Alternate language version of +msgid "alternate-language-version-of" +msgstr "Alternate language version of" + +# Default: Alternate versions +msgid "alternate-versions" +msgstr "Alternate versions" + +# Default: Amazon reviews +msgid "amazon-reviews" +msgstr "Amazon reviews" + +# Default: Analog left +msgid "analog-left" +msgstr "Analog left" + +# Default: Analog right +msgid "analog-right" +msgstr "Analog right" + +# Default: Animation department +msgid "animation-department" +msgstr "Animation department" + +# Default: Archive footage +msgid "archive-footage" +msgstr "Archive footage" + +# Default: Arithmetic mean +msgid "arithmetic-mean" +msgstr "Arithmetic mean" + +# Default: Art department +msgid "art-department" +msgstr "Art department" + +# Default: Art direction +msgid "art-direction" +msgstr "Art direction" + +# Default: Art director +msgid "art-director" +msgstr "Art director" + +# Default: Article +msgid "article" +msgstr "Article" + +# Default: Asin +msgid "asin" +msgstr "Asin" + +# Default: Aspect ratio +msgid "aspect-ratio" +msgstr "Aspect ratio" + +# Default: Assigner +msgid "assigner" +msgstr "Assigner" + +# Default: Assistant director +msgid "assistant-director" +msgstr "Assistant director" + +# Default: Auctions +msgid "auctions" +msgstr "Auctions" + +# Default: Audio noise +msgid "audio-noise" +msgstr "Audio noise" + +# Default: Audio quality +msgid "audio-quality" +msgstr "Audio quality" + +# Default: Award +msgid "award" +msgstr "Award" + +# Default: Awards +msgid "awards" +msgstr "Awards" + +# Default: Biographical movies +msgid "biographical-movies" +msgstr "Biographical movies" + +# Default: Biography +msgid "biography" +msgstr "Biography" + +# Default: Biography print +msgid "biography-print" +msgstr "Biography print" + +# Default: Birth date +msgid "birth-date" +msgstr "Birth date" + +# Default: Birth name +msgid "birth-name" +msgstr "Birth name" + +# Default: Birth notes +msgid "birth-notes" +msgstr "Birth notes" + +# Default: Body +msgid "body" +msgstr "Body" + +# Default: Book +msgid "book" +msgstr "Book" + +# Default: Books +msgid "books" +msgstr "Books" + +# Default: Bottom 100 rank +msgid "bottom-100-rank" +msgstr "Bottom 100 rank" + +# Default: Budget +msgid "budget" +msgstr "Budget" + +# Default: Business +msgid "business" +msgstr "Business" + +# Default: By arrangement with +msgid "by-arrangement-with" +msgstr "By arrangement with" + +# Default: Camera +msgid "camera" +msgstr "Camera" + +# Default: Camera and electrical department +msgid "camera-and-electrical-department" +msgstr "Camera and electrical department" + +# Default: Canonical episode title +msgid "canonical-episode-title" +msgstr "Canonical episode title" + +# Default: Canonical name +msgid "canonical-name" +msgstr "Canonical name" + +# Default: Canonical series title +msgid "canonical-series-title" +msgstr "Canonical series title" + +# Default: Canonical title +msgid "canonical-title" +msgstr "Canonical title" + +# Default: Cast +msgid "cast" +msgstr "Cast" + +# Default: Casting department +msgid "casting-department" +msgstr "Casting department" + +# Default: Casting director +msgid "casting-director" +msgstr "Casting director" + +# Default: Catalog number +msgid "catalog-number" +msgstr "Catalog number" + +# Default: Category +msgid "category" +msgstr "Category" + +# Default: Certificate +msgid "certificate" +msgstr "Certificate" + +# Default: Certificates +msgid "certificates" +msgstr "Certificates" + +# Default: Certification +msgid "certification" +msgstr "Certification" + +# Default: Channel +msgid "channel" +msgstr "Channel" + +# Default: Character +msgid "character" +msgstr "Character" + +# Default: Cinematographer +msgid "cinematographer" +msgstr "Cinematographer" + +# Default: Cinematographic process +msgid "cinematographic-process" +msgstr "Cinematographic process" + +# Default: Close captions teletext ld g +msgid "close-captions-teletext-ld-g" +msgstr "Close captions teletext ld g" + +# Default: Color info +msgid "color-info" +msgstr "Color info" + +# Default: Color information +msgid "color-information" +msgstr "Color information" + +# Default: Color rendition +msgid "color-rendition" +msgstr "Color rendition" + +# Default: Company +msgid "company" +msgstr "Company" + +# Default: Complete cast +msgid "complete-cast" +msgstr "Complete cast" + +# Default: Complete crew +msgid "complete-crew" +msgstr "Complete crew" + +# Default: Composer +msgid "composer" +msgstr "Composer" + +# Default: Connections +msgid "connections" +msgstr "Connections" + +# Default: Contrast +msgid "contrast" +msgstr "Contrast" + +# Default: Copyright holder +msgid "copyright-holder" +msgstr "Copyright holder" + +# Default: Costume department +msgid "costume-department" +msgstr "Costume department" + +# Default: Costume designer +msgid "costume-designer" +msgstr "Costume designer" + +# Default: Countries +msgid "countries" +msgstr "Countries" + +# Default: Country +msgid "country" +msgstr "Country" + +# Default: Courtesy of +msgid "courtesy-of" +msgstr "Courtesy of" + +# Default: Cover +msgid "cover" +msgstr "Cover" + +# Default: Cover url +msgid "cover-url" +msgstr "Cover url" + +# Default: Crazy credits +msgid "crazy-credits" +msgstr "Crazy credits" + +# Default: Creator +msgid "creator" +msgstr "Creator" + +# Default: Current role +msgid "current-role" +msgstr "Current role" + +# Default: Database +msgid "database" +msgstr "Database" + +# Default: Date +msgid "date" +msgstr "Date" + +# Default: Death date +msgid "death-date" +msgstr "Death date" + +# Default: Death notes +msgid "death-notes" +msgstr "Death notes" + +# Default: Demographic +msgid "demographic" +msgstr "Demographic" + +# Default: Description +msgid "description" +msgstr "Description" + +# Default: Dialogue intellegibility +msgid "dialogue-intellegibility" +msgstr "Dialogue intellegibility" + +# Default: Digital sound +msgid "digital-sound" +msgstr "Digital sound" + +# Default: Director +msgid "director" +msgstr "Director" + +# Default: Disc format +msgid "disc-format" +msgstr "Disc format" + +# Default: Disc size +msgid "disc-size" +msgstr "Disc size" + +# Default: Distributors +msgid "distributors" +msgstr "Distributors" + +# Default: Dvd +msgid "dvd" +msgstr "Dvd" + +# Default: Dvd features +msgid "dvd-features" +msgstr "Dvd features" + +# Default: Dvd format +msgid "dvd-format" +msgstr "Dvd format" + +# Default: Dvds +msgid "dvds" +msgstr "Dvds" + +# Default: Dynamic range +msgid "dynamic-range" +msgstr "Dynamic range" + +# Default: Edited from +msgid "edited-from" +msgstr "Edited from" + +# Default: Edited into +msgid "edited-into" +msgstr "Edited into" + +# Default: Editor +msgid "editor" +msgstr "Editor" + +# Default: Editorial department +msgid "editorial-department" +msgstr "Editorial department" + +# Default: Episode +msgid "episode" +msgstr "Episode" + +# Default: Episode of +msgid "episode-of" +msgstr "Episode of" + +# Default: Episode title +msgid "episode-title" +msgstr "Episode title" + +# Default: Episodes +msgid "episodes" +msgstr "Episodes" + +# Default: Episodes rating +msgid "episodes-rating" +msgstr "Episodes rating" + +# Default: Essays +msgid "essays" +msgstr "Essays" + +# Default: External reviews +msgid "external-reviews" +msgstr "External reviews" + +# Default: Faqs +msgid "faqs" +msgstr "Faqs" + +# Default: Featured in +msgid "featured-in" +msgstr "Featured in" + +# Default: Features +msgid "features" +msgstr "Features" + +# Default: Film negative format +msgid "film-negative-format" +msgstr "Film negative format" + +# Default: Filming dates +msgid "filming-dates" +msgstr "Filming dates" + +# Default: Filmography +msgid "filmography" +msgstr "Filmography" + +# Default: Followed by +msgid "followed-by" +msgstr "Followed by" + +# Default: Follows +msgid "follows" +msgstr "Follows" + +# Default: For +msgid "for" +msgstr "For" + +# Default: Frequency response +msgid "frequency-response" +msgstr "Frequency response" + +# Default: From +msgid "from" +msgstr "From" + +# Default: Full article link +msgid "full-article-link" +msgstr "Full article link" + +# Default: Genres +msgid "genres" +msgstr "Genres" + +# Default: Goofs +msgid "goofs" +msgstr "Goofs" + +# Default: Gross +msgid "gross" +msgstr "Gross" + +# Default: Group genre +msgid "group-genre" +msgstr "Group genre" + +# Default: Headshot +msgid "headshot" +msgstr "Headshot" + +# Default: Height +msgid "height" +msgstr "Height" + +# Default: Imdbindex +msgid "imdbindex" +msgstr "Imdbindex" + +# Default: Interview +msgid "interview" +msgstr "Interview" + +# Default: Interviews +msgid "interviews" +msgstr "Interviews" + +# Default: Introduction +msgid "introduction" +msgstr "Introduction" + +# Default: Item +msgid "item" +msgstr "Item" + +# Default: Keywords +msgid "keywords" +msgstr "Keywords" + +# Default: Kind +msgid "kind" +msgstr "Kind" + +# Default: Label +msgid "label" +msgstr "Label" + +# Default: Laboratory +msgid "laboratory" +msgstr "Laboratory" + +# Default: Language +msgid "language" +msgstr "Language" + +# Default: Languages +msgid "languages" +msgstr "Languages" + +# Default: Laserdisc +msgid "laserdisc" +msgstr "Laserdisc" + +# Default: Laserdisc title +msgid "laserdisc-title" +msgstr "Laserdisc title" + +# Default: Length +msgid "length" +msgstr "Length" + +# Default: Line +msgid "line" +msgstr "Line" + +# Default: Link +msgid "link" +msgstr "Link" + +# Default: Link text +msgid "link-text" +msgstr "Link text" + +# Default: Literature +msgid "literature" +msgstr "Literature" + +# Default: Locations +msgid "locations" +msgstr "Locations" + +# Default: Long imdb canonical name +msgid "long-imdb-canonical-name" +msgstr "Long imdb canonical name" + +# Default: Long imdb canonical title +msgid "long-imdb-canonical-title" +msgstr "Long imdb canonical title" + +# Default: Long imdb episode title +msgid "long-imdb-episode-title" +msgstr "Long imdb episode title" + +# Default: Long imdb name +msgid "long-imdb-name" +msgstr "Long imdb name" + +# Default: Long imdb title +msgid "long-imdb-title" +msgstr "Long imdb title" + +# Default: Magazine cover photo +msgid "magazine-cover-photo" +msgstr "Magazine cover photo" + +# Default: Make up +msgid "make-up" +msgstr "Make up" + +# Default: Master format +msgid "master-format" +msgstr "Master format" + +# Default: Median +msgid "median" +msgstr "Median" + +# Default: Merchandising links +msgid "merchandising-links" +msgstr "Merchandising links" + +# Default: Mini biography +msgid "mini-biography" +msgstr "Mini biography" + +# Default: Misc links +msgid "misc-links" +msgstr "Misc links" + +# Default: Miscellaneous companies +msgid "miscellaneous-companies" +msgstr "Miscellaneous companies" + +# Default: Miscellaneous crew +msgid "miscellaneous-crew" +msgstr "Miscellaneous crew" + +# Default: Movie +msgid "movie" +msgstr "Movie" + +# Default: Mpaa +msgid "mpaa" +msgstr "Mpaa" + +# Default: Music department +msgid "music-department" +msgstr "Music department" + +# Default: Name +msgid "name" +msgstr "Name" + +# Default: News +msgid "news" +msgstr "News" + +# Default: Newsgroup reviews +msgid "newsgroup-reviews" +msgstr "Newsgroup reviews" + +# Default: Nick names +msgid "nick-names" +msgstr "Nick names" + +# Default: Notes +msgid "notes" +msgstr "Notes" + +# Default: Novel +msgid "novel" +msgstr "Novel" + +# Default: Number +msgid "number" +msgstr "Number" + +# Default: Number of chapter stops +msgid "number-of-chapter-stops" +msgstr "Number of chapter stops" + +# Default: Number of episodes +msgid "number-of-episodes" +msgstr "Number of episodes" + +# Default: Number of seasons +msgid "number-of-seasons" +msgstr "Number of seasons" + +# Default: Number of sides +msgid "number-of-sides" +msgstr "Number of sides" + +# Default: Number of votes +msgid "number-of-votes" +msgstr "Number of votes" + +# Default: Official retail price +msgid "official-retail-price" +msgstr "Official retail price" + +# Default: Official sites +msgid "official-sites" +msgstr "Official sites" + +# Default: Opening weekend +msgid "opening-weekend" +msgstr "Opening weekend" + +# Default: Original air date +msgid "original-air-date" +msgstr "Original air date" + +# Default: Original music +msgid "original-music" +msgstr "Original music" + +# Default: Original title +msgid "original-title" +msgstr "Original title" + +# Default: Other literature +msgid "other-literature" +msgstr "Other literature" + +# Default: Other works +msgid "other-works" +msgstr "Other works" + +# Default: Parents guide +msgid "parents-guide" +msgstr "Parents guide" + +# Default: Performed by +msgid "performed-by" +msgstr "Performed by" + +# Default: Person +msgid "person" +msgstr "Person" + +# Default: Photo sites +msgid "photo-sites" +msgstr "Photo sites" + +# Default: Pictorial +msgid "pictorial" +msgstr "Pictorial" + +# Default: Picture format +msgid "picture-format" +msgstr "Picture format" + +# Default: Plot +msgid "plot" +msgstr "Plot" + +# Default: Plot outline +msgid "plot-outline" +msgstr "Plot outline" + +# Default: Portrayed in +msgid "portrayed-in" +msgstr "Portrayed in" + +# Default: Pressing plant +msgid "pressing-plant" +msgstr "Pressing plant" + +# Default: Printed film format +msgid "printed-film-format" +msgstr "Printed film format" + +# Default: Printed media reviews +msgid "printed-media-reviews" +msgstr "Printed media reviews" + +# Default: Producer +msgid "producer" +msgstr "Producer" + +# Default: Production companies +msgid "production-companies" +msgstr "Production companies" + +# Default: Production country +msgid "production-country" +msgstr "Production country" + +# Default: Production dates +msgid "production-dates" +msgstr "Production dates" + +# Default: Production design +msgid "production-design" +msgstr "Production design" + +# Default: Production designer +msgid "production-designer" +msgstr "Production designer" + +# Default: Production manager +msgid "production-manager" +msgstr "Production manager" + +# Default: Production process protocol +msgid "production-process-protocol" +msgstr "Production process protocol" + +# Default: Quality of source +msgid "quality-of-source" +msgstr "Quality of source" + +# Default: Quality program +msgid "quality-program" +msgstr "Quality program" + +# Default: Quote +msgid "quote" +msgstr "Quote" + +# Default: Quotes +msgid "quotes" +msgstr "Quotes" + +# Default: Rating +msgid "rating" +msgstr "Rating" + +# Default: Recommendations +msgid "recommendations" +msgstr "Recommendations" + +# Default: Referenced in +msgid "referenced-in" +msgstr "Referenced in" + +# Default: References +msgid "references" +msgstr "References" + +# Default: Region +msgid "region" +msgstr "Region" + +# Default: Release country +msgid "release-country" +msgstr "Release country" + +# Default: Release date +msgid "release-date" +msgstr "Release date" + +# Default: Release dates +msgid "release-dates" +msgstr "Release dates" + +# Default: Remade as +msgid "remade-as" +msgstr "Remade as" + +# Default: Remake of +msgid "remake-of" +msgstr "Remake of" + +# Default: Rentals +msgid "rentals" +msgstr "Rentals" + +# Default: Result +msgid "result" +msgstr "Result" + +# Default: Review +msgid "review" +msgstr "Review" + +# Default: Review author +msgid "review-author" +msgstr "Review author" + +# Default: Review kind +msgid "review-kind" +msgstr "Review kind" + +# Default: Runtime +msgid "runtime" +msgstr "Runtime" + +# Default: Runtimes +msgid "runtimes" +msgstr "Runtimes" + +# Default: Salary history +msgid "salary-history" +msgstr "Salary history" + +# Default: Screenplay teleplay +msgid "screenplay-teleplay" +msgstr "Screenplay teleplay" + +# Default: Season +msgid "season" +msgstr "Season" + +# Default: Second unit director or assistant director +msgid "second-unit-director-or-assistant-director" +msgstr "Second unit director or assistant director" + +# Default: Self +msgid "self" +msgstr "Self" + +# Default: Series animation department +msgid "series-animation-department" +msgstr "Series animation department" + +# Default: Series art department +msgid "series-art-department" +msgstr "Series art department" + +# Default: Series assistant directors +msgid "series-assistant-directors" +msgstr "Series assistant directors" + +# Default: Series camera department +msgid "series-camera-department" +msgstr "Series camera department" + +# Default: Series casting department +msgid "series-casting-department" +msgstr "Series casting department" + +# Default: Series cinematographers +msgid "series-cinematographers" +msgstr "Series cinematographers" + +# Default: Series costume department +msgid "series-costume-department" +msgstr "Series costume department" + +# Default: Series editorial department +msgid "series-editorial-department" +msgstr "Series editorial department" + +# Default: Series editors +msgid "series-editors" +msgstr "Series editors" + +# Default: Series make up department +msgid "series-make-up-department" +msgstr "Series make up department" + +# Default: Series miscellaneous +msgid "series-miscellaneous" +msgstr "Series miscellaneous" + +# Default: Series music department +msgid "series-music-department" +msgstr "Series music department" + +# Default: Series producers +msgid "series-producers" +msgstr "Series producers" + +# Default: Series production designers +msgid "series-production-designers" +msgstr "Series production designers" + +# Default: Series production managers +msgid "series-production-managers" +msgstr "Series production managers" + +# Default: Series sound department +msgid "series-sound-department" +msgstr "Series sound department" + +# Default: Series special effects department +msgid "series-special-effects-department" +msgstr "Series special effects department" + +# Default: Series stunts +msgid "series-stunts" +msgstr "Series stunts" + +# Default: Series title +msgid "series-title" +msgstr "Series title" + +# Default: Series transportation department +msgid "series-transportation-department" +msgstr "Series transportation department" + +# Default: Series visual effects department +msgid "series-visual-effects-department" +msgstr "Series visual effects department" + +# Default: Series writers +msgid "series-writers" +msgstr "Series writers" + +# Default: Series years +msgid "series-years" +msgstr "Series years" + +# Default: Set decoration +msgid "set-decoration" +msgstr "Set decoration" + +# Default: Sharpness +msgid "sharpness" +msgstr "Sharpness" + +# Default: Similar to +msgid "similar-to" +msgstr "Similar to" + +# Default: Sound clips +msgid "sound-clips" +msgstr "Sound clips" + +# Default: Sound crew +msgid "sound-crew" +msgstr "Sound crew" + +# Default: Sound encoding +msgid "sound-encoding" +msgstr "Sound encoding" + +# Default: Sound mix +msgid "sound-mix" +msgstr "Sound mix" + +# Default: Soundtrack +msgid "soundtrack" +msgstr "Soundtrack" + +# Default: Spaciality +msgid "spaciality" +msgstr "Spaciality" + +# Default: Special effects +msgid "special-effects" +msgstr "Special effects" + +# Default: Special effects companies +msgid "special-effects-companies" +msgstr "Special effects companies" + +# Default: Special effects department +msgid "special-effects-department" +msgstr "Special effects department" + +# Default: Spin off +msgid "spin-off" +msgstr "Spin off" + +# Default: Spin off from +msgid "spin-off-from" +msgstr "Spin off from" + +# Default: Spoofed in +msgid "spoofed-in" +msgstr "Spoofed in" + +# Default: Spoofs +msgid "spoofs" +msgstr "Spoofs" + +# Default: Spouse +msgid "spouse" +msgstr "Spouse" + +# Default: Status of availablility +msgid "status-of-availablility" +msgstr "Status of availablility" + +# Default: Studio +msgid "studio" +msgstr "Studio" + +# Default: Studios +msgid "studios" +msgstr "Studios" + +# Default: Stunt performer +msgid "stunt-performer" +msgstr "Stunt performer" + +# Default: Stunts +msgid "stunts" +msgstr "Stunts" + +# Default: Subtitles +msgid "subtitles" +msgstr "Subtitles" + +# Default: Supplement +msgid "supplement" +msgstr "Supplement" + +# Default: Supplements +msgid "supplements" +msgstr "Supplements" + +# Default: Synopsis +msgid "synopsis" +msgstr "Synopsis" + +# Default: Taglines +msgid "taglines" +msgstr "Taglines" + +# Default: Tech info +msgid "tech-info" +msgstr "Tech info" + +# Default: Thanks +msgid "thanks" +msgstr "Thanks" + +# Default: Time +msgid "time" +msgstr "Time" + +# Default: Title +msgid "title" +msgstr "Title" + +# Default: Titles in this product +msgid "titles-in-this-product" +msgstr "Titles in this product" + +# Default: To +msgid "to" +msgstr "To" + +# Default: Top 250 rank +msgid "top-250-rank" +msgstr "Top 250 rank" + +# Default: Trade mark +msgid "trade-mark" +msgstr "Trade mark" + +# Default: Transportation department +msgid "transportation-department" +msgstr "Transportation department" + +# Default: Trivia +msgid "trivia" +msgstr "Trivia" + +# Default: Under license from +msgid "under-license-from" +msgstr "Under license from" + +# Default: Unknown link +msgid "unknown-link" +msgstr "Unknown link" + +# Default: Upc +msgid "upc" +msgstr "Upc" + +# Default: Version of +msgid "version-of" +msgstr "Version of" + +# Default: Vhs +msgid "vhs" +msgstr "Vhs" + +# Default: Video artifacts +msgid "video-artifacts" +msgstr "Video artifacts" + +# Default: Video clips +msgid "video-clips" +msgstr "Video clips" + +# Default: Video noise +msgid "video-noise" +msgstr "Video noise" + +# Default: Video quality +msgid "video-quality" +msgstr "Video quality" + +# Default: Video standard +msgid "video-standard" +msgstr "Video standard" + +# Default: Visual effects +msgid "visual-effects" +msgstr "Visual effects" + +# Default: Votes +msgid "votes" +msgstr "Votes" + +# Default: Votes distribution +msgid "votes-distribution" +msgstr "Votes distribution" + +# Default: Weekend gross +msgid "weekend-gross" +msgstr "Weekend gross" + +# Default: Where now +msgid "where-now" +msgstr "Where now" + +# Default: With +msgid "with" +msgstr "With" + +# Default: Writer +msgid "writer" +msgstr "Writer" + +# Default: Written by +msgid "written-by" +msgstr "Written by" + +# Default: Year +msgid "year" +msgstr "Year" + +# Default: Zshops +msgid "zshops" +msgstr "Zshops" + diff --git a/libs/imdb/locale/imdbpy-it.po b/libs/imdb/locale/imdbpy-it.po new file mode 100644 index 00000000..17cfce46 --- /dev/null +++ b/libs/imdb/locale/imdbpy-it.po @@ -0,0 +1,1300 @@ +# Gettext message file for imdbpy +msgid "" +msgstr "" +"Project-Id-Version: imdbpy\n" +"POT-Creation-Date: 2010-03-18 14:35+0000\n" +"PO-Revision-Date: 2009-07-03 13:00+0000\n" +"Last-Translator: Davide Alberani \n" +"Language-Team: Davide Alberani \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language-Code: it\n" +"Language-Name: Italian\n" +"Preferred-Encodings: utf-8\n" +"Domain: imdbpy\n" + +# Default: Actor +msgid "actor" +msgstr "Attore" + +# Default: Actress +msgid "actress" +msgstr "Attrice" + +# Default: Adaption +msgid "adaption" +msgstr "Adattamento" + +# Default: Additional information +msgid "additional-information" +msgstr "Ulteriori informazioni" + +# Default: Admissions +msgid "admissions" +msgstr "Biglietti venduti" + +# Default: Agent address +msgid "agent-address" +msgstr "Indirizzo dell'agente" + +# Default: Airing +msgid "airing" +msgstr "In onda" + +# Default: Akas +msgid "akas" +msgstr "Alias" + +# Default: Akas from release info +msgid "akas-from-release-info" +msgstr "Alias dalle informazioni di rilascio" + +# Default: All products +msgid "all-products" +msgstr "Tutti i prodotti" + +# Default: Alternate language version of +msgid "alternate-language-version-of" +msgstr "Versione in altra lingua di" + +# Default: Alternate versions +msgid "alternate-versions" +msgstr "Versioni alternative" + +# Default: Amazon reviews +msgid "amazon-reviews" +msgstr "Recensione di Amazon" + +# Default: Analog left +msgid "analog-left" +msgstr "Analogico sinistro" + +# Default: Analog right +msgid "analog-right" +msgstr "Analogico destro" + +# Default: Animation department +msgid "animation-department" +msgstr "Dipartimento animazione" + +# Default: Archive footage +msgid "archive-footage" +msgstr "Materiale d'archivio" + +# Default: Arithmetic mean +msgid "arithmetic-mean" +msgstr "Media aritmetica" + +# Default: Art department +msgid "art-department" +msgstr "Dipartimento artistico" + +# Default: Art direction +msgid "art-direction" +msgstr "Direzione artistica" + +# Default: Art director +msgid "art-director" +msgstr "Direttore artistico" + +# Default: Article +msgid "article" +msgstr "Articolo" + +# Default: Asin +msgid "asin" +msgstr "Asin" + +# Default: Aspect ratio +msgid "aspect-ratio" +msgstr "Rapporto d'aspetto" + +# Default: Assigner +msgid "assigner" +msgstr "Assegnatario" + +# Default: Assistant director +msgid "assistant-director" +msgstr "Assistente regista" + +# Default: Auctions +msgid "auctions" +msgstr "Aste" + +# Default: Audio noise +msgid "audio-noise" +msgstr "Rumore audio" + +# Default: Audio quality +msgid "audio-quality" +msgstr "Qualità audio" + +# Default: Award +msgid "award" +msgstr "Premio" + +# Default: Awards +msgid "awards" +msgstr "Premi" + +# Default: Biographical movies +msgid "biographical-movies" +msgstr "Film biografici" + +# Default: Biography +msgid "biography" +msgstr "Biografia" + +# Default: Biography print +msgid "biography-print" +msgstr "Biografia" + +# Default: Birth date +msgid "birth-date" +msgstr "Data di nascita" + +# Default: Birth name +msgid "birth-name" +msgstr "Nome di nascita" + +# Default: Birth notes +msgid "birth-notes" +msgstr "Note di nascita" + +# Default: Body +msgid "body" +msgstr "Corpo" + +# Default: Book +msgid "book" +msgstr "Libro" + +# Default: Books +msgid "books" +msgstr "Libri" + +# Default: Bottom 100 rank +msgid "bottom-100-rank" +msgstr "Posizione nella bottom 100" + +# Default: Budget +msgid "budget" +msgstr "Bilancio" + +# Default: Business +msgid "business" +msgstr "Affari" + +# Default: By arrangement with +msgid "by-arrangement-with" +msgstr "Arrangiamento con" + +# Default: Camera +msgid "camera" +msgstr "Cinepresa" + +# Default: Camera and electrical department +msgid "camera-and-electrical-department" +msgstr "Cinepresa e dipartimento elettrico" + +# Default: Canonical episode title +msgid "canonical-episode-title" +msgstr "Titolo dell'episodio in forma canonica" + +# Default: Canonical name +msgid "canonical-name" +msgstr "Nome in forma canonica" + +# Default: Canonical series title +msgid "canonical-series-title" +msgstr "Titolo della serie in forma canonica" + +# Default: Canonical title +msgid "canonical-title" +msgstr "Titolo in forma canonica" + +# Default: Cast +msgid "cast" +msgstr "Cast" + +# Default: Casting department +msgid "casting-department" +msgstr "Casting" + +# Default: Casting director +msgid "casting-director" +msgstr "Direttore del casting" + +# Default: Catalog number +msgid "catalog-number" +msgstr "Numero di catalogo" + +# Default: Category +msgid "category" +msgstr "Categoria" + +# Default: Certificate +msgid "certificate" +msgstr "Certificazione" + +# Default: Certificates +msgid "certificates" +msgstr "Certificazioni" + +# Default: Certification +msgid "certification" +msgstr "Certificazioni" + +# Default: Channel +msgid "channel" +msgstr "Canale" + +# Default: Character +msgid "character" +msgstr "Personaggio" + +# Default: Cinematographer +msgid "cinematographer" +msgstr "Fotografia" + +# Default: Cinematographic process +msgid "cinematographic-process" +msgstr "Processo cinematografico" + +# Default: Close captions teletext ld g +msgid "close-captions-teletext-ld-g" +msgstr "" + +# Default: Color info +msgid "color-info" +msgstr "Colore" + +# Default: Color information +msgid "color-information" +msgstr "Informazioni sul colore" + +# Default: Color rendition +msgid "color-rendition" +msgstr "Resa dei colori" + +# Default: Company +msgid "company" +msgstr "Compagnia" + +# Default: Complete cast +msgid "complete-cast" +msgstr "Cast completo" + +# Default: Complete crew +msgid "complete-crew" +msgstr "Troupe completa" + +# Default: Composer +msgid "composer" +msgstr "Compositore" + +# Default: Connections +msgid "connections" +msgstr "Collegamenti" + +# Default: Contrast +msgid "contrast" +msgstr "Contrasto" + +# Default: Copyright holder +msgid "copyright-holder" +msgstr "Detentore dei diritti d'autore" + +# Default: Costume department +msgid "costume-department" +msgstr "Dipartimento costumi" + +# Default: Costume designer +msgid "costume-designer" +msgstr "Costumista" + +# Default: Countries +msgid "countries" +msgstr "Paesi" + +# Default: Country +msgid "country" +msgstr "Paese" + +# Default: Courtesy of +msgid "courtesy-of" +msgstr "Cortesia di" + +# Default: Cover +msgid "cover" +msgstr "Copertina" + +# Default: Cover url +msgid "cover-url" +msgstr "Locandina" + +# Default: Crazy credits +msgid "crazy-credits" +msgstr "Titoli pazzi" + +# Default: Creator +msgid "creator" +msgstr "Creatore" + +# Default: Current role +msgid "current-role" +msgstr "Ruolo" + +# Default: Database +msgid "database" +msgstr "Database" + +# Default: Date +msgid "date" +msgstr "Data" + +# Default: Death date +msgid "death-date" +msgstr "Data di morte" + +# Default: Death notes +msgid "death-notes" +msgstr "Note di morte" + +# Default: Demographic +msgid "demographic" +msgstr "Spaccato demografico" + +# Default: Description +msgid "description" +msgstr "Descrizione" + +# Default: Dialogue intellegibility +msgid "dialogue-intellegibility" +msgstr "Comprensibilità dei dialoghi" + +# Default: Digital sound +msgid "digital-sound" +msgstr "Suono digitale" + +# Default: Director +msgid "director" +msgstr "Regista" + +# Default: Disc format +msgid "disc-format" +msgstr "Formato del disco" + +# Default: Disc size +msgid "disc-size" +msgstr "Dimensione del disco" + +# Default: Distributors +msgid "distributors" +msgstr "Distributori" + +# Default: Dvd +msgid "dvd" +msgstr "Dvd" + +# Default: Dvd features +msgid "dvd-features" +msgstr "Caratteristiche del DVD" + +# Default: Dvd format +msgid "dvd-format" +msgstr "Formato del DVD" + +# Default: Dvds +msgid "dvds" +msgstr "Dvd" + +# Default: Dynamic range +msgid "dynamic-range" +msgstr "Intervallo dinamico" + +# Default: Edited from +msgid "edited-from" +msgstr "Tratto da" + +# Default: Edited into +msgid "edited-into" +msgstr "Montato in" + +# Default: Editor +msgid "editor" +msgstr "Editore" + +# Default: Editorial department +msgid "editorial-department" +msgstr "Dipartimento editoriale" + +# Default: Episode +msgid "episode" +msgstr "Episodio" + +# Default: Episode of +msgid "episode-of" +msgstr "Episodio di" + +# Default: Episode title +msgid "episode-title" +msgstr "Titolo dell'episodio" + +# Default: Episodes +msgid "episodes" +msgstr "Episodi" + +# Default: Episodes rating +msgid "episodes-rating" +msgstr "Voto degli episodi" + +# Default: Essays +msgid "essays" +msgstr "Saggi" + +# Default: External reviews +msgid "external-reviews" +msgstr "Recensioni esterne" + +# Default: Faqs +msgid "faqs" +msgstr "Domande ricorrenti" + +# Default: Feature +msgid "feature" +msgstr "Caratteristica" + +# Default: Featured in +msgid "featured-in" +msgstr "Ripreso in" + +# Default: Features +msgid "features" +msgstr "Caratteristiche" + +# Default: Film negative format +msgid "film-negative-format" +msgstr "Formato del negativo" + +# Default: Filming dates +msgid "filming-dates" +msgstr "Data delle riprese" + +# Default: Filmography +msgid "filmography" +msgstr "Filmografia" + +# Default: Followed by +msgid "followed-by" +msgstr "Seguito da" + +# Default: Follows +msgid "follows" +msgstr "Segue" + +# Default: For +msgid "for" +msgstr "Per" + +# Default: Frequency response +msgid "frequency-response" +msgstr "Frequenze di risposta" + +# Default: From +msgid "from" +msgstr "Da" + +# Default: Full article link +msgid "full-article-link" +msgstr "Collegamento all'articolo completo" + +# Default: Full size cover url +msgid "full-size-cover-url" +msgstr "URL della copertina nelle dimensioni originali" + +# Default: Full size headshot +msgid "full-size-headshot" +msgstr "Ritratto nelle dimensioni originali" + +# Default: Genres +msgid "genres" +msgstr "Generi" + +# Default: Goofs +msgid "goofs" +msgstr "Errori" + +# Default: Gross +msgid "gross" +msgstr "Lordo" + +# Default: Group genre +msgid "group-genre" +msgstr "" + +# Default: Headshot +msgid "headshot" +msgstr "Foto" + +# Default: Height +msgid "height" +msgstr "Altezza" + +# Default: Imdbindex +msgid "imdbindex" +msgstr "" + +# Default: In development +msgid "in-development" +msgstr "In sviluppo" + +# Default: Interview +msgid "interview" +msgstr "Intervista" + +# Default: Interviews +msgid "interviews" +msgstr "Interviste" + +# Default: Introduction +msgid "introduction" +msgstr "Introduzione" + +# Default: Item +msgid "item" +msgstr "Elemento" + +# Default: Keywords +msgid "keywords" +msgstr "Parole chiave" + +# Default: Kind +msgid "kind" +msgstr "Tipo" + +# Default: Label +msgid "label" +msgstr "Etichetta" + +# Default: Laboratory +msgid "laboratory" +msgstr "Laboratorio" + +# Default: Language +msgid "language" +msgstr "Lingua" + +# Default: Languages +msgid "languages" +msgstr "Lingue" + +# Default: Laserdisc +msgid "laserdisc" +msgstr "Laserdisc" + +# Default: Laserdisc title +msgid "laserdisc-title" +msgstr "Titolo del laserdisc" + +# Default: Length +msgid "length" +msgstr "Durata" + +# Default: Line +msgid "line" +msgstr "Battuta" + +# Default: Link +msgid "link" +msgstr "Collegamento" + +# Default: Link text +msgid "link-text" +msgstr "Testo del link" + +# Default: Literature +msgid "literature" +msgstr "Letteratura" + +# Default: Locations +msgid "locations" +msgstr "Luoghi" + +# Default: Long imdb canonical name +msgid "long-imdb-canonical-name" +msgstr "Nome canonico IMDb lungo" + +# Default: Long imdb canonical title +msgid "long-imdb-canonical-title" +msgstr "Titolo canonico IMDb lungo" + +# Default: Long imdb episode title +msgid "long-imdb-episode-title" +msgstr "Titolo dell'episodio canonico IMDb lungo" + +# Default: Long imdb name +msgid "long-imdb-name" +msgstr "Nome IMDb lungo" + +# Default: Long imdb title +msgid "long-imdb-title" +msgstr "Titolo IMDb lungo" + +# Default: Magazine cover photo +msgid "magazine-cover-photo" +msgstr "Foto di copertina" + +# Default: Make up +msgid "make-up" +msgstr "Trucco" + +# Default: Master format +msgid "master-format" +msgstr "Formato del master" + +# Default: Median +msgid "median" +msgstr "Mediana" + +# Default: Merchandising links +msgid "merchandising-links" +msgstr "Collegamenti al merchandising" + +# Default: Mini biography +msgid "mini-biography" +msgstr "Biografia" + +# Default: Misc links +msgid "misc-links" +msgstr "Altri collegamenti" + +# Default: Miscellaneous companies +msgid "miscellaneous-companies" +msgstr "Altre compagnie" + +# Default: Miscellaneous crew +msgid "miscellaneous-crew" +msgstr "Altra troupe" + +# Default: Movie +msgid "movie" +msgstr "Film" + +# Default: Mpaa +msgid "mpaa" +msgstr "Visto MPAA" + +# Default: Music department +msgid "music-department" +msgstr "Dipartimento musicale" + +# Default: Name +msgid "name" +msgstr "Nome" + +# Default: News +msgid "news" +msgstr "Notizie" + +# Default: Newsgroup reviews +msgid "newsgroup-reviews" +msgstr "Recensioni dai gruppi di discussione" + +# Default: Nick names +msgid "nick-names" +msgstr "Soprannomi" + +# Default: Notes +msgid "notes" +msgstr "Note" + +# Default: Novel +msgid "novel" +msgstr "Novella" + +# Default: Number +msgid "number" +msgstr "Numero" + +# Default: Number of chapter stops +msgid "number-of-chapter-stops" +msgstr "Numero di interruzioni di capitolo" + +# Default: Number of episodes +msgid "number-of-episodes" +msgstr "Numero di episodi" + +# Default: Number of seasons +msgid "number-of-seasons" +msgstr "Numero di stagioni" + +# Default: Number of sides +msgid "number-of-sides" +msgstr "Numero di lati" + +# Default: Number of votes +msgid "number-of-votes" +msgstr "Numero di voti" + +# Default: Official retail price +msgid "official-retail-price" +msgstr "Prezzo ufficiale al pubblico" + +# Default: Official sites +msgid "official-sites" +msgstr "Siti ufficiali" + +# Default: Opening weekend +msgid "opening-weekend" +msgstr "Weekend d'apertura" + +# Default: Original air date +msgid "original-air-date" +msgstr "Data della prima messa in onda" + +# Default: Original music +msgid "original-music" +msgstr "Musica originale" + +# Default: Original title +msgid "original-title" +msgstr "Titolo originale" + +# Default: Other literature +msgid "other-literature" +msgstr "Altre opere letterarie" + +# Default: Other works +msgid "other-works" +msgstr "Altri lavori" + +# Default: Parents guide +msgid "parents-guide" +msgstr "Guida per i genitori" + +# Default: Performed by +msgid "performed-by" +msgstr "Eseguito da" + +# Default: Person +msgid "person" +msgstr "Persona" + +# Default: Photo sites +msgid "photo-sites" +msgstr "Siti con fotografie" + +# Default: Pictorial +msgid "pictorial" +msgstr "Ritratto" + +# Default: Picture format +msgid "picture-format" +msgstr "Formato dell'immagine" + +# Default: Plot +msgid "plot" +msgstr "Trama" + +# Default: Plot outline +msgid "plot-outline" +msgstr "Trama in breve" + +# Default: Portrayed in +msgid "portrayed-in" +msgstr "Rappresentato in" + +# Default: Pressing plant +msgid "pressing-plant" +msgstr "Impianto di stampa" + +# Default: Printed film format +msgid "printed-film-format" +msgstr "Formato della pellicola" + +# Default: Printed media reviews +msgid "printed-media-reviews" +msgstr "Recensioni su carta stampata" + +# Default: Producer +msgid "producer" +msgstr "Produttore" + +# Default: Production companies +msgid "production-companies" +msgstr "Compagnie di produzione" + +# Default: Production country +msgid "production-country" +msgstr "Paese di produzione" + +# Default: Production dates +msgid "production-dates" +msgstr "Date di produzione" + +# Default: Production design +msgid "production-design" +msgstr "Design di produzione" + +# Default: Production designer +msgid "production-designer" +msgstr "Designer di produzione" + +# Default: Production manager +msgid "production-manager" +msgstr "Manager di produzione" + +# Default: Production process protocol +msgid "production-process-protocol" +msgstr "Controllo del processo di produzione" + +# Default: Quality of source +msgid "quality-of-source" +msgstr "Qualità dell'originale" + +# Default: Quality program +msgid "quality-program" +msgstr "Programma di Qualità" + +# Default: Quote +msgid "quote" +msgstr "Citazione" + +# Default: Quotes +msgid "quotes" +msgstr "Citazioni" + +# Default: Rating +msgid "rating" +msgstr "Voto" + +# Default: Recommendations +msgid "recommendations" +msgstr "Raccomandazioni" + +# Default: Referenced in +msgid "referenced-in" +msgstr "Citato in" + +# Default: References +msgid "references" +msgstr "Cita" + +# Default: Region +msgid "region" +msgstr "Regione" + +# Default: Release country +msgid "release-country" +msgstr "Paese d'uscita" + +# Default: Release date +msgid "release-date" +msgstr "Data d'uscita" + +# Default: Release dates +msgid "release-dates" +msgstr "Date d'uscita" + +# Default: Remade as +msgid "remade-as" +msgstr "Rifatto come" + +# Default: Remake of +msgid "remake-of" +msgstr "Rifacimento di" + +# Default: Rentals +msgid "rentals" +msgstr "Noleggi" + +# Default: Result +msgid "result" +msgstr "Risultato" + +# Default: Review +msgid "review" +msgstr "Recensione" + +# Default: Review author +msgid "review-author" +msgstr "Autore della recensione" + +# Default: Review kind +msgid "review-kind" +msgstr "Tipo di recensione" + +# Default: Runtime +msgid "runtime" +msgstr "Durata" + +# Default: Runtimes +msgid "runtimes" +msgstr "Durate" + +# Default: Salary history +msgid "salary-history" +msgstr "Stipendi" + +# Default: Screenplay teleplay +msgid "screenplay-teleplay" +msgstr "" + +# Default: Season +msgid "season" +msgstr "Stagione" + +# Default: Second unit director or assistant director +msgid "second-unit-director-or-assistant-director" +msgstr "Regista della seconda unità o aiuto regista" + +# Default: Self +msgid "self" +msgstr "Se stesso" + +# Default: Series animation department +msgid "series-animation-department" +msgstr "Dipartimento animazione della serie" + +# Default: Series art department +msgid "series-art-department" +msgstr "Dipartimento artistico della serie" + +# Default: Series assistant directors +msgid "series-assistant-directors" +msgstr "Assistenti registi della serie" + +# Default: Series camera department +msgid "series-camera-department" +msgstr "" + +# Default: Series casting department +msgid "series-casting-department" +msgstr "" + +# Default: Series cinematographers +msgid "series-cinematographers" +msgstr "" + +# Default: Series costume department +msgid "series-costume-department" +msgstr "" + +# Default: Series editorial department +msgid "series-editorial-department" +msgstr "" + +# Default: Series editors +msgid "series-editors" +msgstr "" + +# Default: Series make up department +msgid "series-make-up-department" +msgstr "" + +# Default: Series miscellaneous +msgid "series-miscellaneous" +msgstr "" + +# Default: Series music department +msgid "series-music-department" +msgstr "" + +# Default: Series producers +msgid "series-producers" +msgstr "" + +# Default: Series production designers +msgid "series-production-designers" +msgstr "" + +# Default: Series production managers +msgid "series-production-managers" +msgstr "" + +# Default: Series sound department +msgid "series-sound-department" +msgstr "Dipartimento sonoro della serie" + +# Default: Series special effects department +msgid "series-special-effects-department" +msgstr "Dipartimento effetti speciali della serie" + +# Default: Series stunts +msgid "series-stunts" +msgstr "Controfigure della serie" + +# Default: Series title +msgid "series-title" +msgstr "Titolo della serie" + +# Default: Series transportation department +msgid "series-transportation-department" +msgstr "" + +# Default: Series visual effects department +msgid "series-visual-effects-department" +msgstr "" + +# Default: Series writers +msgid "series-writers" +msgstr "Scrittori della serie" + +# Default: Series years +msgid "series-years" +msgstr "Anni della serie" + +# Default: Set decoration +msgid "set-decoration" +msgstr "Decorazione del set" + +# Default: Sharpness +msgid "sharpness" +msgstr "" + +# Default: Similar to +msgid "similar-to" +msgstr "Simile a" + +# Default: Smart canonical episode title +msgid "smart-canonical-episode-title" +msgstr "Titolo canonico intelligente dell'episodio" + +# Default: Smart canonical series title +msgid "smart-canonical-series-title" +msgstr "Titolo canonico intelligente della serie" + +# Default: Smart canonical title +msgid "smart-canonical-title" +msgstr "Titolo canonico intelligente" + +# Default: Smart long imdb canonical title +msgid "smart-long-imdb-canonical-title" +msgstr "Titolo canonico lungo intelligente" + +# Default: Sound clips +msgid "sound-clips" +msgstr "" + +# Default: Sound crew +msgid "sound-crew" +msgstr "" + +# Default: Sound encoding +msgid "sound-encoding" +msgstr "Codifica sonora" + +# Default: Sound mix +msgid "sound-mix" +msgstr "Mix audio" + +# Default: Soundtrack +msgid "soundtrack" +msgstr "Colonna sonora" + +# Default: Spaciality +msgid "spaciality" +msgstr "Specialità" + +# Default: Special effects +msgid "special-effects" +msgstr "Effetti speciali" + +# Default: Special effects companies +msgid "special-effects-companies" +msgstr "Compagnie di effetti speciali" + +# Default: Special effects department +msgid "special-effects-department" +msgstr "Dipartimento effetti speciali" + +# Default: Spin off +msgid "spin-off" +msgstr "Derivati" + +# Default: Spin off from +msgid "spin-off-from" +msgstr "Deriva da" + +# Default: Spoofed in +msgid "spoofed-in" +msgstr "Preso in giro in" + +# Default: Spoofs +msgid "spoofs" +msgstr "Prende in giro" + +# Default: Spouse +msgid "spouse" +msgstr "Coniuge" + +# Default: Status of availablility +msgid "status-of-availablility" +msgstr "Disponibilità" + +# Default: Studio +msgid "studio" +msgstr "Studio" + +# Default: Studios +msgid "studios" +msgstr "Studi" + +# Default: Stunt performer +msgid "stunt-performer" +msgstr "" + +# Default: Stunts +msgid "stunts" +msgstr "Stuntman" + +# Default: Subtitles +msgid "subtitles" +msgstr "Sottotitoli" + +# Default: Supplement +msgid "supplement" +msgstr "Extra" + +# Default: Supplements +msgid "supplements" +msgstr "Extra" + +# Default: Synopsis +msgid "synopsis" +msgstr "Compendio della trama" + +# Default: Taglines +msgid "taglines" +msgstr "Slogan" + +# Default: Tech info +msgid "tech-info" +msgstr "Informazioni tecniche" + +# Default: Thanks +msgid "thanks" +msgstr "Ringraziamenti" + +# Default: Time +msgid "time" +msgstr "Tempo" + +# Default: Title +msgid "title" +msgstr "Titolo" + +# Default: Titles in this product +msgid "titles-in-this-product" +msgstr "Titoli in questo prodotto" + +# Default: To +msgid "to" +msgstr "A" + +# Default: Top 250 rank +msgid "top-250-rank" +msgstr "Posizione nella top 250" + +# Default: Trade mark +msgid "trade-mark" +msgstr "Marchio registrato" + +# Default: Transportation department +msgid "transportation-department" +msgstr "Dipartimento trasporti" + +# Default: Trivia +msgid "trivia" +msgstr "Frivolezze" + +# Default: Tv +msgid "tv" +msgstr "Tv" + +# Default: Under license from +msgid "under-license-from" +msgstr "Sotto licenza da" + +# Default: Unknown link +msgid "unknown-link" +msgstr "Collegamento sconosciuto" + +# Default: Upc +msgid "upc" +msgstr "" + +# Default: Version of +msgid "version-of" +msgstr "Versione di" + +# Default: Vhs +msgid "vhs" +msgstr "VHS" + +# Default: Video +msgid "video" +msgstr "Video" + +# Default: Video artifacts +msgid "video-artifacts" +msgstr "Imperfezioni video" + +# Default: Video clips +msgid "video-clips" +msgstr "Video clips" + +# Default: Video noise +msgid "video-noise" +msgstr "Rumore video" + +# Default: Video quality +msgid "video-quality" +msgstr "Qualità video" + +# Default: Video standard +msgid "video-standard" +msgstr "Standard video" + +# Default: Visual effects +msgid "visual-effects" +msgstr "Effetti visivi" + +# Default: Votes +msgid "votes" +msgstr "Voti" + +# Default: Votes distribution +msgid "votes-distribution" +msgstr "Distribuzione dei voti" + +# Default: Weekend gross +msgid "weekend-gross" +msgstr "Lordo del primo fine settimana" + +# Default: Where now +msgid "where-now" +msgstr "Cosa sta facendo ora" + +# Default: With +msgid "with" +msgstr "Con" + +# Default: Writer +msgid "writer" +msgstr "Scrittore" + +# Default: Written by +msgid "written-by" +msgstr "Scritto da" + +# Default: Year +msgid "year" +msgstr "Anno" + +# Default: Zshops +msgid "zshops" +msgstr "" diff --git a/libs/imdb/locale/imdbpy-tr.po b/libs/imdb/locale/imdbpy-tr.po new file mode 100644 index 00000000..a44452ae --- /dev/null +++ b/libs/imdb/locale/imdbpy-tr.po @@ -0,0 +1,1300 @@ +# Gettext message file for imdbpy +msgid "" +msgstr "" +"Project-Id-Version: imdbpy\n" +"POT-Creation-Date: 2010-03-18 14:35+0000\n" +"PO-Revision-Date: 2009-04-21 19:04+0200\n" +"Last-Translator: H. Turgut Uyar \n" +"Language-Team: IMDbPY Türkçe \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Language-Code: tr\n" +"Language-Name: Türkçe\n" +"Preferred-Encodings: utf-8\n" +"Domain: imdbpy\n" + +# Default: Actor +msgid "actor" +msgstr "Oyuncu" + +# Default: Actress +msgid "actress" +msgstr "Oyuncu" + +# Default: Adaption +msgid "adaption" +msgstr "" + +# Default: Additional information +msgid "additional-information" +msgstr "Ek bilgi" + +# Default: Admissions +msgid "admissions" +msgstr "" + +# Default: Agent address +msgid "agent-address" +msgstr "" + +# Default: Airing +msgid "airing" +msgstr "Yayımlanma" + +# Default: Akas +msgid "akas" +msgstr "Diğer başlıklar" + +# Default: Akas from release info +msgid "akas-from-release-info" +msgstr "" + +# Default: All products +msgid "all-products" +msgstr "Bütün ürünler" + +# Default: Alternate language version of +msgid "alternate-language-version-of" +msgstr "" + +# Default: Alternate versions +msgid "alternate-versions" +msgstr "" + +# Default: Amazon reviews +msgid "amazon-reviews" +msgstr "Amazon eleştirileri" + +# Default: Analog left +msgid "analog-left" +msgstr "Analog sol" + +# Default: Analog right +msgid "analog-right" +msgstr "Analog sağ" + +# Default: Animation department +msgid "animation-department" +msgstr "Animasyon departmanı" + +# Default: Archive footage +msgid "archive-footage" +msgstr "Arşiv çekimleri" + +# Default: Arithmetic mean +msgid "arithmetic-mean" +msgstr "Aritmetik ortalama" + +# Default: Art department +msgid "art-department" +msgstr "Sanat departmanı" + +# Default: Art direction +msgid "art-direction" +msgstr "Sanat yönetmenliği" + +# Default: Art director +msgid "art-director" +msgstr "Sanat yönetmeni" + +# Default: Article +msgid "article" +msgstr "" + +# Default: Asin +msgid "asin" +msgstr "ASIN" + +# Default: Aspect ratio +msgid "aspect-ratio" +msgstr "En-boy oranı" + +# Default: Assigner +msgid "assigner" +msgstr "Veren" + +# Default: Assistant director +msgid "assistant-director" +msgstr "Yardımcı yönetmen" + +# Default: Auctions +msgid "auctions" +msgstr "Açık artırmalar" + +# Default: Audio noise +msgid "audio-noise" +msgstr "Ses gürültüsü" + +# Default: Audio quality +msgid "audio-quality" +msgstr "Ses kalitesi" + +# Default: Award +msgid "award" +msgstr "Ödül" + +# Default: Awards +msgid "awards" +msgstr "Ödüller" + +# Default: Biographical movies +msgid "biographical-movies" +msgstr "Biyografik filmler" + +# Default: Biography +msgid "biography" +msgstr "Biyografi" + +# Default: Biography print +msgid "biography-print" +msgstr "Basılı biyografi" + +# Default: Birth date +msgid "birth-date" +msgstr "Doğum tarihi" + +# Default: Birth name +msgid "birth-name" +msgstr "Asıl ismi" + +# Default: Birth notes +msgid "birth-notes" +msgstr "Doğum notları" + +# Default: Body +msgid "body" +msgstr "Metin" + +# Default: Book +msgid "book" +msgstr "Kitap" + +# Default: Books +msgid "books" +msgstr "Kitaplar" + +# Default: Bottom 100 rank +msgid "bottom-100-rank" +msgstr "En kötü 100 içindeki sırası" + +# Default: Budget +msgid "budget" +msgstr "Bütçe" + +# Default: Business +msgid "business" +msgstr "Gişe" + +# Default: By arrangement with +msgid "by-arrangement-with" +msgstr "" + +# Default: Camera +msgid "camera" +msgstr "Kamera" + +# Default: Camera and electrical department +msgid "camera-and-electrical-department" +msgstr "Kamera ve elektrik departmanı" + +# Default: Canonical episode title +msgid "canonical-episode-title" +msgstr "" + +# Default: Canonical name +msgid "canonical-name" +msgstr "" + +# Default: Canonical series title +msgid "canonical-series-title" +msgstr "" + +# Default: Canonical title +msgid "canonical-title" +msgstr "" + +# Default: Cast +msgid "cast" +msgstr "Oynayanlar" + +# Default: Casting department +msgid "casting-department" +msgstr "Oyuncu seçme departmanı" + +# Default: Casting director +msgid "casting-director" +msgstr "Oyuncu seçme yönetmeni" + +# Default: Catalog number +msgid "catalog-number" +msgstr "Katalog numarası" + +# Default: Category +msgid "category" +msgstr "Kategori" + +# Default: Certificate +msgid "certificate" +msgstr "Sertifika" + +# Default: Certificates +msgid "certificates" +msgstr "Sertifikalar" + +# Default: Certification +msgid "certification" +msgstr "" + +# Default: Channel +msgid "channel" +msgstr "Kanal" + +# Default: Character +msgid "character" +msgstr "Karakter" + +# Default: Cinematographer +msgid "cinematographer" +msgstr "Kameraman" + +# Default: Cinematographic process +msgid "cinematographic-process" +msgstr "" + +# Default: Close captions teletext ld g +msgid "close-captions-teletext-ld-g" +msgstr "" + +# Default: Color info +msgid "color-info" +msgstr "Renk bilgisi" + +# Default: Color information +msgid "color-information" +msgstr "Renk bilgisi" + +# Default: Color rendition +msgid "color-rendition" +msgstr "" + +# Default: Company +msgid "company" +msgstr "Şirket" + +# Default: Complete cast +msgid "complete-cast" +msgstr "Bütün oynayanlar" + +# Default: Complete crew +msgid "complete-crew" +msgstr "Bütün çalışanlar" + +# Default: Composer +msgid "composer" +msgstr "Besteci" + +# Default: Connections +msgid "connections" +msgstr "Bağlantılar" + +# Default: Contrast +msgid "contrast" +msgstr "Kontrast" + +# Default: Copyright holder +msgid "copyright-holder" +msgstr "Telif sahibi" + +# Default: Costume department +msgid "costume-department" +msgstr "Kostüm departmanı" + +# Default: Costume designer +msgid "costume-designer" +msgstr "Kostüm tasarımcısı" + +# Default: Countries +msgid "countries" +msgstr "Ülkeler" + +# Default: Country +msgid "country" +msgstr "Ülke" + +# Default: Courtesy of +msgid "courtesy-of" +msgstr "" + +# Default: Cover +msgid "cover" +msgstr "Poster" + +# Default: Cover url +msgid "cover-url" +msgstr "Poster adresi" + +# Default: Crazy credits +msgid "crazy-credits" +msgstr "" + +# Default: Creator +msgid "creator" +msgstr "Yaratıcı" + +# Default: Current role +msgid "current-role" +msgstr "Şimdiki rol" + +# Default: Database +msgid "database" +msgstr "Veritabanı" + +# Default: Date +msgid "date" +msgstr "Tarih" + +# Default: Death date +msgid "death-date" +msgstr "Ölüm tarihi" + +# Default: Death notes +msgid "death-notes" +msgstr "Ölüm notları" + +# Default: Demographic +msgid "demographic" +msgstr "Demografi" + +# Default: Description +msgid "description" +msgstr "Tarif" + +# Default: Dialogue intellegibility +msgid "dialogue-intellegibility" +msgstr "" + +# Default: Digital sound +msgid "digital-sound" +msgstr "Dijital ses" + +# Default: Director +msgid "director" +msgstr "Yönetmen" + +# Default: Disc format +msgid "disc-format" +msgstr "Disk formatı" + +# Default: Disc size +msgid "disc-size" +msgstr "Disk boyu" + +# Default: Distributors +msgid "distributors" +msgstr "Dağıtıcılar" + +# Default: Dvd +msgid "dvd" +msgstr "DVD" + +# Default: Dvd features +msgid "dvd-features" +msgstr "DVD özellikleri" + +# Default: Dvd format +msgid "dvd-format" +msgstr "DVD formatı" + +# Default: Dvds +msgid "dvds" +msgstr "DVD'ler" + +# Default: Dynamic range +msgid "dynamic-range" +msgstr "" + +# Default: Edited from +msgid "edited-from" +msgstr "" + +# Default: Edited into +msgid "edited-into" +msgstr "" + +# Default: Editor +msgid "editor" +msgstr "Montajcı" + +# Default: Editorial department +msgid "editorial-department" +msgstr "Montaj departmanı" + +# Default: Episode +msgid "episode" +msgstr "Bölüm" + +# Default: Episode of +msgid "episode-of" +msgstr "Dizi" + +# Default: Episode title +msgid "episode-title" +msgstr "Bölüm başlığı" + +# Default: Episodes +msgid "episodes" +msgstr "Bölümler" + +# Default: Episodes rating +msgid "episodes-rating" +msgstr "Bölüm puanı" + +# Default: Essays +msgid "essays" +msgstr "Denemeler" + +# Default: External reviews +msgid "external-reviews" +msgstr "Harici eleştiriler" + +# Default: Faqs +msgid "faqs" +msgstr "SSS" + +# Default: Feature +msgid "feature" +msgstr "" + +# Default: Featured in +msgid "featured-in" +msgstr "" + +# Default: Features +msgid "features" +msgstr "" + +# Default: Film negative format +msgid "film-negative-format" +msgstr "Film negatif formatı" + +# Default: Filming dates +msgid "filming-dates" +msgstr "Çekim tarihleri" + +# Default: Filmography +msgid "filmography" +msgstr "Filmografi" + +# Default: Followed by +msgid "followed-by" +msgstr "Peşinden gelen film" + +# Default: Follows +msgid "follows" +msgstr "Peşinden geldiği film" + +# Default: For +msgid "for" +msgstr "Film" + +# Default: Frequency response +msgid "frequency-response" +msgstr "" + +# Default: From +msgid "from" +msgstr "" + +# Default: Full article link +msgid "full-article-link" +msgstr "" + +# Default: Full size cover url +msgid "full-size-cover-url" +msgstr "" + +# Default: Full size headshot +msgid "full-size-headshot" +msgstr "" + +# Default: Genres +msgid "genres" +msgstr "Türler" + +# Default: Goofs +msgid "goofs" +msgstr "Hatalar" + +# Default: Gross +msgid "gross" +msgstr "Hasılat" + +# Default: Group genre +msgid "group-genre" +msgstr "" + +# Default: Headshot +msgid "headshot" +msgstr "Resim" + +# Default: Height +msgid "height" +msgstr "Boy" + +# Default: Imdbindex +msgid "imdbindex" +msgstr "" + +# Default: In development +msgid "in-development" +msgstr "" + +# Default: Interview +msgid "interview" +msgstr "Söyleşi" + +# Default: Interviews +msgid "interviews" +msgstr "Söyleşiler" + +# Default: Introduction +msgid "introduction" +msgstr "İlk filmi" + +# Default: Item +msgid "item" +msgstr "" + +# Default: Keywords +msgid "keywords" +msgstr "Anahtar sözcükler" + +# Default: Kind +msgid "kind" +msgstr "Tip" + +# Default: Label +msgid "label" +msgstr "" + +# Default: Laboratory +msgid "laboratory" +msgstr "Laboratuar" + +# Default: Language +msgid "language" +msgstr "Dil" + +# Default: Languages +msgid "languages" +msgstr "Diller" + +# Default: Laserdisc +msgid "laserdisc" +msgstr "Lazer Disk" + +# Default: Laserdisc title +msgid "laserdisc-title" +msgstr "" + +# Default: Length +msgid "length" +msgstr "Süre" + +# Default: Line +msgid "line" +msgstr "Replik" + +# Default: Link +msgid "link" +msgstr "Bağlantı" + +# Default: Link text +msgid "link-text" +msgstr "Bağlantı metni" + +# Default: Literature +msgid "literature" +msgstr "Edebiyat" + +# Default: Locations +msgid "locations" +msgstr "Çekim yerleri" + +# Default: Long imdb canonical name +msgid "long-imdb-canonical-name" +msgstr "" + +# Default: Long imdb canonical title +msgid "long-imdb-canonical-title" +msgstr "" + +# Default: Long imdb episode title +msgid "long-imdb-episode-title" +msgstr "IMDb uzun bölüm başlığı" + +# Default: Long imdb name +msgid "long-imdb-name" +msgstr "IMDb uzun ismi" + +# Default: Long imdb title +msgid "long-imdb-title" +msgstr "IMDb uzun başlığı" + +# Default: Magazine cover photo +msgid "magazine-cover-photo" +msgstr "Dergi kapağı resmi" + +# Default: Make up +msgid "make-up" +msgstr "Makyaj" + +# Default: Master format +msgid "master-format" +msgstr "Master format" + +# Default: Median +msgid "median" +msgstr "Orta değer" + +# Default: Merchandising links +msgid "merchandising-links" +msgstr "" + +# Default: Mini biography +msgid "mini-biography" +msgstr "Mini biyografi" + +# Default: Misc links +msgid "misc-links" +msgstr "" + +# Default: Miscellaneous companies +msgid "miscellaneous-companies" +msgstr "" + +# Default: Miscellaneous crew +msgid "miscellaneous-crew" +msgstr "" + +# Default: Movie +msgid "movie" +msgstr "Film" + +# Default: Mpaa +msgid "mpaa" +msgstr "MPAA" + +# Default: Music department +msgid "music-department" +msgstr "Müzik departmanı" + +# Default: Name +msgid "name" +msgstr "İsim" + +# Default: News +msgid "news" +msgstr "Haberler" + +# Default: Newsgroup reviews +msgid "newsgroup-reviews" +msgstr "Haber grubu eleştirileri" + +# Default: Nick names +msgid "nick-names" +msgstr "Takma isimler" + +# Default: Notes +msgid "notes" +msgstr "Notlar" + +# Default: Novel +msgid "novel" +msgstr "Roman" + +# Default: Number +msgid "number" +msgstr "Sayı" + +# Default: Number of chapter stops +msgid "number-of-chapter-stops" +msgstr "" + +# Default: Number of episodes +msgid "number-of-episodes" +msgstr "Bölüm sayısı" + +# Default: Number of seasons +msgid "number-of-seasons" +msgstr "Sezon sayısı" + +# Default: Number of sides +msgid "number-of-sides" +msgstr "" + +# Default: Number of votes +msgid "number-of-votes" +msgstr "Oy sayısı" + +# Default: Official retail price +msgid "official-retail-price" +msgstr "Resmi perakende satış fiyatı" + +# Default: Official sites +msgid "official-sites" +msgstr "Resmi siteler" + +# Default: Opening weekend +msgid "opening-weekend" +msgstr "Açılış haftasonu" + +# Default: Original air date +msgid "original-air-date" +msgstr "İlk yayımlanma tarihi" + +# Default: Original music +msgid "original-music" +msgstr "Orijinal müzik" + +# Default: Original title +msgid "original-title" +msgstr "" + +# Default: Other literature +msgid "other-literature" +msgstr "" + +# Default: Other works +msgid "other-works" +msgstr "Diğer çalışmalar" + +# Default: Parents guide +msgid "parents-guide" +msgstr "Ana-baba kılavuzu" + +# Default: Performed by +msgid "performed-by" +msgstr "İcra eden" + +# Default: Person +msgid "person" +msgstr "Kişi" + +# Default: Photo sites +msgid "photo-sites" +msgstr "Fotoğraf siteleri" + +# Default: Pictorial +msgid "pictorial" +msgstr "" + +# Default: Picture format +msgid "picture-format" +msgstr "Resim formatı" + +# Default: Plot +msgid "plot" +msgstr "Konu" + +# Default: Plot outline +msgid "plot-outline" +msgstr "Konu kısa özeti" + +# Default: Portrayed in +msgid "portrayed-in" +msgstr "" + +# Default: Pressing plant +msgid "pressing-plant" +msgstr "" + +# Default: Printed film format +msgid "printed-film-format" +msgstr "Basılı film formatı" + +# Default: Printed media reviews +msgid "printed-media-reviews" +msgstr "Basın eleştirileri" + +# Default: Producer +msgid "producer" +msgstr "Yapımcı" + +# Default: Production companies +msgid "production-companies" +msgstr "Yapım şirketleri" + +# Default: Production country +msgid "production-country" +msgstr "Yapımcı ülke" + +# Default: Production dates +msgid "production-dates" +msgstr "Yapım tarihleri" + +# Default: Production design +msgid "production-design" +msgstr "Yapım tasarımı" + +# Default: Production designer +msgid "production-designer" +msgstr "Yapım tasarımcısı" + +# Default: Production manager +msgid "production-manager" +msgstr "Yapım yöneticisi" + +# Default: Production process protocol +msgid "production-process-protocol" +msgstr "" + +# Default: Quality of source +msgid "quality-of-source" +msgstr "" + +# Default: Quality program +msgid "quality-program" +msgstr "" + +# Default: Quote +msgid "quote" +msgstr "Alıntı" + +# Default: Quotes +msgid "quotes" +msgstr "Alıntılar" + +# Default: Rating +msgid "rating" +msgstr "Puan" + +# Default: Recommendations +msgid "recommendations" +msgstr "Tavsiyeler" + +# Default: Referenced in +msgid "referenced-in" +msgstr "Gönderme yapılan filmler" + +# Default: References +msgid "references" +msgstr "Gönderme yaptığı filmler" + +# Default: Region +msgid "region" +msgstr "Bölge" + +# Default: Release country +msgid "release-country" +msgstr "" + +# Default: Release date +msgid "release-date" +msgstr "" + +# Default: Release dates +msgid "release-dates" +msgstr "" + +# Default: Remade as +msgid "remade-as" +msgstr "Yeniden çekilişi" + +# Default: Remake of +msgid "remake-of" +msgstr "Yeniden çekimi olduğu film" + +# Default: Rentals +msgid "rentals" +msgstr "Kiralamalar" + +# Default: Result +msgid "result" +msgstr "Sonuç" + +# Default: Review +msgid "review" +msgstr "Eleştiri" + +# Default: Review author +msgid "review-author" +msgstr "Eleştiri yazarı" + +# Default: Review kind +msgid "review-kind" +msgstr "Eleştiri tipi" + +# Default: Runtime +msgid "runtime" +msgstr "Süre" + +# Default: Runtimes +msgid "runtimes" +msgstr "Süreler" + +# Default: Salary history +msgid "salary-history" +msgstr "Üçret tarihçesi" + +# Default: Screenplay teleplay +msgid "screenplay-teleplay" +msgstr "Senaryo" + +# Default: Season +msgid "season" +msgstr "Sezon" + +# Default: Second unit director or assistant director +msgid "second-unit-director-or-assistant-director" +msgstr "İkinci birim yönetmeni ya da yardımcı yönetmen" + +# Default: Self +msgid "self" +msgstr "Kendisi" + +# Default: Series animation department +msgid "series-animation-department" +msgstr "Dizinin animasyon departmanı" + +# Default: Series art department +msgid "series-art-department" +msgstr "Dizinin sanat departmanı" + +# Default: Series assistant directors +msgid "series-assistant-directors" +msgstr "Dizinin yardımcı yönetmenleri" + +# Default: Series camera department +msgid "series-camera-department" +msgstr "Dizinin kamera departmanı" + +# Default: Series casting department +msgid "series-casting-department" +msgstr "Dizinin oyuncu seçimi departmanı" + +# Default: Series cinematographers +msgid "series-cinematographers" +msgstr "Dizinin kameramanları" + +# Default: Series costume department +msgid "series-costume-department" +msgstr "Dizinin kostüm departmanı" + +# Default: Series editorial department +msgid "series-editorial-department" +msgstr "Dizinin montaj departmanı" + +# Default: Series editors +msgid "series-editors" +msgstr "Dizinin montajcıları" + +# Default: Series make up department +msgid "series-make-up-department" +msgstr "Dizinin makyaj departmanı" + +# Default: Series miscellaneous +msgid "series-miscellaneous" +msgstr "" + +# Default: Series music department +msgid "series-music-department" +msgstr "Dizinin müzik departmanı" + +# Default: Series producers +msgid "series-producers" +msgstr "Dizinin yapımcıları" + +# Default: Series production designers +msgid "series-production-designers" +msgstr "Dizinin yapım tasarımcıları" + +# Default: Series production managers +msgid "series-production-managers" +msgstr "Dizinin yapım yöneticileri" + +# Default: Series sound department +msgid "series-sound-department" +msgstr "Dizinin ses departmanı" + +# Default: Series special effects department +msgid "series-special-effects-department" +msgstr "Dizinin özel efekt departmanı" + +# Default: Series stunts +msgid "series-stunts" +msgstr "Dizinin dublörleri" + +# Default: Series title +msgid "series-title" +msgstr "Dizinin başlığı" + +# Default: Series transportation department +msgid "series-transportation-department" +msgstr "Dizinin ulaşım departmanı" + +# Default: Series visual effects department +msgid "series-visual-effects-department" +msgstr "Dizinin görsel efekt departmanı" + +# Default: Series writers +msgid "series-writers" +msgstr "Dizinin yazarları" + +# Default: Series years +msgid "series-years" +msgstr "Dizinin yılları" + +# Default: Set decoration +msgid "set-decoration" +msgstr "Set dekorasyonu" + +# Default: Sharpness +msgid "sharpness" +msgstr "Keskinlik" + +# Default: Similar to +msgid "similar-to" +msgstr "Benzer" + +# Default: Smart canonical episode title +msgid "smart-canonical-episode-title" +msgstr "" + +# Default: Smart canonical series title +msgid "smart-canonical-series-title" +msgstr "" + +# Default: Smart canonical title +msgid "smart-canonical-title" +msgstr "" + +# Default: Smart long imdb canonical title +msgid "smart-long-imdb-canonical-title" +msgstr "" + +# Default: Sound clips +msgid "sound-clips" +msgstr "Ses klipleri" + +# Default: Sound crew +msgid "sound-crew" +msgstr "Ses ekibi" + +# Default: Sound encoding +msgid "sound-encoding" +msgstr "Ses kodlaması" + +# Default: Sound mix +msgid "sound-mix" +msgstr "" + +# Default: Soundtrack +msgid "soundtrack" +msgstr "Film müzikleri" + +# Default: Spaciality +msgid "spaciality" +msgstr "" + +# Default: Special effects +msgid "special-effects" +msgstr "Özel efektler" + +# Default: Special effects companies +msgid "special-effects-companies" +msgstr "Özel efekt şirketleri" + +# Default: Special effects department +msgid "special-effects-department" +msgstr "Özel efekt departmanı" + +# Default: Spin off +msgid "spin-off" +msgstr "" + +# Default: Spin off from +msgid "spin-off-from" +msgstr "" + +# Default: Spoofed in +msgid "spoofed-in" +msgstr "Dalga geçildiği filmler" + +# Default: Spoofs +msgid "spoofs" +msgstr "Dalga geçtiği filmler" + +# Default: Spouse +msgid "spouse" +msgstr "Eşi" + +# Default: Status of availablility +msgid "status-of-availablility" +msgstr "" + +# Default: Studio +msgid "studio" +msgstr "Stüdyo" + +# Default: Studios +msgid "studios" +msgstr "Stüdyolar" + +# Default: Stunt performer +msgid "stunt-performer" +msgstr "" + +# Default: Stunts +msgid "stunts" +msgstr "Dublörler" + +# Default: Subtitles +msgid "subtitles" +msgstr "Altyazılar" + +# Default: Supplement +msgid "supplement" +msgstr "" + +# Default: Supplements +msgid "supplements" +msgstr "" + +# Default: Synopsis +msgid "synopsis" +msgstr "Sinopsis" + +# Default: Taglines +msgid "taglines" +msgstr "Spotlar" + +# Default: Tech info +msgid "tech-info" +msgstr "Teknik bilgi" + +# Default: Thanks +msgid "thanks" +msgstr "Teşekkürler" + +# Default: Time +msgid "time" +msgstr "Zaman" + +# Default: Title +msgid "title" +msgstr "Başlık" + +# Default: Titles in this product +msgid "titles-in-this-product" +msgstr "Bu üründeki başlıklar" + +# Default: To +msgid "to" +msgstr "Alan" + +# Default: Top 250 rank +msgid "top-250-rank" +msgstr "En iyi 250 içindeki sırası" + +# Default: Trade mark +msgid "trade-mark" +msgstr "Kendine has özelliği" + +# Default: Transportation department +msgid "transportation-department" +msgstr "Ulaşım departmanı" + +# Default: Trivia +msgid "trivia" +msgstr "İlginç notlar" + +# Default: Tv +msgid "tv" +msgstr "" + +# Default: Under license from +msgid "under-license-from" +msgstr "" + +# Default: Unknown link +msgid "unknown-link" +msgstr "" + +# Default: Upc +msgid "upc" +msgstr "" + +# Default: Version of +msgid "version-of" +msgstr "" + +# Default: Vhs +msgid "vhs" +msgstr "VHS" + +# Default: Video +msgid "video" +msgstr "" + +# Default: Video artifacts +msgid "video-artifacts" +msgstr "" + +# Default: Video clips +msgid "video-clips" +msgstr "Video klipleri" + +# Default: Video noise +msgid "video-noise" +msgstr "Video gürültüsü" + +# Default: Video quality +msgid "video-quality" +msgstr "Video kalitesi" + +# Default: Video standard +msgid "video-standard" +msgstr "Video standardı" + +# Default: Visual effects +msgid "visual-effects" +msgstr "Görsel efektler" + +# Default: Votes +msgid "votes" +msgstr "Oylar" + +# Default: Votes distribution +msgid "votes-distribution" +msgstr "Oyların dağılımı" + +# Default: Weekend gross +msgid "weekend-gross" +msgstr "Haftasonu hasılatı" + +# Default: Where now +msgid "where-now" +msgstr "Şu anda nerede" + +# Default: With +msgid "with" +msgstr "" + +# Default: Writer +msgid "writer" +msgstr "Yazar" + +# Default: Written by +msgid "written-by" +msgstr "Yazan" + +# Default: Year +msgid "year" +msgstr "Yıl" + +# Default: Zshops +msgid "zshops" +msgstr "ZShops" diff --git a/libs/imdb/locale/imdbpy.pot b/libs/imdb/locale/imdbpy.pot new file mode 100644 index 00000000..14ac1669 --- /dev/null +++ b/libs/imdb/locale/imdbpy.pot @@ -0,0 +1,1301 @@ +# Gettext message file for imdbpy +msgid "" +msgstr "" +"Project-Id-Version: imdbpy\n" +"POT-Creation-Date: 2010-03-18 14:35+0000\n" +"PO-Revision-Date: YYYY-MM-DD HH:MM+0000\n" +"Last-Translator: YOUR NAME \n" +"Language-Team: TEAM NAME \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Language-Code: en\n" +"Language-Name: English\n" +"Preferred-Encodings: utf-8\n" +"Domain: imdbpy\n" + +# Default: Actor +msgid "actor" +msgstr "" + +# Default: Actress +msgid "actress" +msgstr "" + +# Default: Adaption +msgid "adaption" +msgstr "" + +# Default: Additional information +msgid "additional-information" +msgstr "" + +# Default: Admissions +msgid "admissions" +msgstr "" + +# Default: Agent address +msgid "agent-address" +msgstr "" + +# Default: Airing +msgid "airing" +msgstr "" + +# Default: Akas +msgid "akas" +msgstr "" + +# Default: Akas from release info +msgid "akas-from-release-info" +msgstr "" + +# Default: All products +msgid "all-products" +msgstr "" + +# Default: Alternate language version of +msgid "alternate-language-version-of" +msgstr "" + +# Default: Alternate versions +msgid "alternate-versions" +msgstr "" + +# Default: Amazon reviews +msgid "amazon-reviews" +msgstr "" + +# Default: Analog left +msgid "analog-left" +msgstr "" + +# Default: Analog right +msgid "analog-right" +msgstr "" + +# Default: Animation department +msgid "animation-department" +msgstr "" + +# Default: Archive footage +msgid "archive-footage" +msgstr "" + +# Default: Arithmetic mean +msgid "arithmetic-mean" +msgstr "" + +# Default: Art department +msgid "art-department" +msgstr "" + +# Default: Art direction +msgid "art-direction" +msgstr "" + +# Default: Art director +msgid "art-director" +msgstr "" + +# Default: Article +msgid "article" +msgstr "" + +# Default: Asin +msgid "asin" +msgstr "" + +# Default: Aspect ratio +msgid "aspect-ratio" +msgstr "" + +# Default: Assigner +msgid "assigner" +msgstr "" + +# Default: Assistant director +msgid "assistant-director" +msgstr "" + +# Default: Auctions +msgid "auctions" +msgstr "" + +# Default: Audio noise +msgid "audio-noise" +msgstr "" + +# Default: Audio quality +msgid "audio-quality" +msgstr "" + +# Default: Award +msgid "award" +msgstr "" + +# Default: Awards +msgid "awards" +msgstr "" + +# Default: Biographical movies +msgid "biographical-movies" +msgstr "" + +# Default: Biography +msgid "biography" +msgstr "" + +# Default: Biography print +msgid "biography-print" +msgstr "" + +# Default: Birth date +msgid "birth-date" +msgstr "" + +# Default: Birth name +msgid "birth-name" +msgstr "" + +# Default: Birth notes +msgid "birth-notes" +msgstr "" + +# Default: Body +msgid "body" +msgstr "" + +# Default: Book +msgid "book" +msgstr "" + +# Default: Books +msgid "books" +msgstr "" + +# Default: Bottom 100 rank +msgid "bottom-100-rank" +msgstr "" + +# Default: Budget +msgid "budget" +msgstr "" + +# Default: Business +msgid "business" +msgstr "" + +# Default: By arrangement with +msgid "by-arrangement-with" +msgstr "" + +# Default: Camera +msgid "camera" +msgstr "" + +# Default: Camera and electrical department +msgid "camera-and-electrical-department" +msgstr "" + +# Default: Canonical episode title +msgid "canonical-episode-title" +msgstr "" + +# Default: Canonical name +msgid "canonical-name" +msgstr "" + +# Default: Canonical series title +msgid "canonical-series-title" +msgstr "" + +# Default: Canonical title +msgid "canonical-title" +msgstr "" + +# Default: Cast +msgid "cast" +msgstr "" + +# Default: Casting department +msgid "casting-department" +msgstr "" + +# Default: Casting director +msgid "casting-director" +msgstr "" + +# Default: Catalog number +msgid "catalog-number" +msgstr "" + +# Default: Category +msgid "category" +msgstr "" + +# Default: Certificate +msgid "certificate" +msgstr "" + +# Default: Certificates +msgid "certificates" +msgstr "" + +# Default: Certification +msgid "certification" +msgstr "" + +# Default: Channel +msgid "channel" +msgstr "" + +# Default: Character +msgid "character" +msgstr "" + +# Default: Cinematographer +msgid "cinematographer" +msgstr "" + +# Default: Cinematographic process +msgid "cinematographic-process" +msgstr "" + +# Default: Close captions teletext ld g +msgid "close-captions-teletext-ld-g" +msgstr "" + +# Default: Color info +msgid "color-info" +msgstr "" + +# Default: Color information +msgid "color-information" +msgstr "" + +# Default: Color rendition +msgid "color-rendition" +msgstr "" + +# Default: Company +msgid "company" +msgstr "" + +# Default: Complete cast +msgid "complete-cast" +msgstr "" + +# Default: Complete crew +msgid "complete-crew" +msgstr "" + +# Default: Composer +msgid "composer" +msgstr "" + +# Default: Connections +msgid "connections" +msgstr "" + +# Default: Contrast +msgid "contrast" +msgstr "" + +# Default: Copyright holder +msgid "copyright-holder" +msgstr "" + +# Default: Costume department +msgid "costume-department" +msgstr "" + +# Default: Costume designer +msgid "costume-designer" +msgstr "" + +# Default: Countries +msgid "countries" +msgstr "" + +# Default: Country +msgid "country" +msgstr "" + +# Default: Courtesy of +msgid "courtesy-of" +msgstr "" + +# Default: Cover +msgid "cover" +msgstr "" + +# Default: Cover url +msgid "cover-url" +msgstr "" + +# Default: Crazy credits +msgid "crazy-credits" +msgstr "" + +# Default: Creator +msgid "creator" +msgstr "" + +# Default: Current role +msgid "current-role" +msgstr "" + +# Default: Database +msgid "database" +msgstr "" + +# Default: Date +msgid "date" +msgstr "" + +# Default: Death date +msgid "death-date" +msgstr "" + +# Default: Death notes +msgid "death-notes" +msgstr "" + +# Default: Demographic +msgid "demographic" +msgstr "" + +# Default: Description +msgid "description" +msgstr "" + +# Default: Dialogue intellegibility +msgid "dialogue-intellegibility" +msgstr "" + +# Default: Digital sound +msgid "digital-sound" +msgstr "" + +# Default: Director +msgid "director" +msgstr "" + +# Default: Disc format +msgid "disc-format" +msgstr "" + +# Default: Disc size +msgid "disc-size" +msgstr "" + +# Default: Distributors +msgid "distributors" +msgstr "" + +# Default: Dvd +msgid "dvd" +msgstr "" + +# Default: Dvd features +msgid "dvd-features" +msgstr "" + +# Default: Dvd format +msgid "dvd-format" +msgstr "" + +# Default: Dvds +msgid "dvds" +msgstr "" + +# Default: Dynamic range +msgid "dynamic-range" +msgstr "" + +# Default: Edited from +msgid "edited-from" +msgstr "" + +# Default: Edited into +msgid "edited-into" +msgstr "" + +# Default: Editor +msgid "editor" +msgstr "" + +# Default: Editorial department +msgid "editorial-department" +msgstr "" + +# Default: Episode +msgid "episode" +msgstr "" + +# Default: Episode of +msgid "episode-of" +msgstr "" + +# Default: Episode title +msgid "episode-title" +msgstr "" + +# Default: Episodes +msgid "episodes" +msgstr "" + +# Default: Episodes rating +msgid "episodes-rating" +msgstr "" + +# Default: Essays +msgid "essays" +msgstr "" + +# Default: External reviews +msgid "external-reviews" +msgstr "" + +# Default: Faqs +msgid "faqs" +msgstr "" + +# Default: Feature +msgid "feature" +msgstr "" + +# Default: Featured in +msgid "featured-in" +msgstr "" + +# Default: Features +msgid "features" +msgstr "" + +# Default: Film negative format +msgid "film-negative-format" +msgstr "" + +# Default: Filming dates +msgid "filming-dates" +msgstr "" + +# Default: Filmography +msgid "filmography" +msgstr "" + +# Default: Followed by +msgid "followed-by" +msgstr "" + +# Default: Follows +msgid "follows" +msgstr "" + +# Default: For +msgid "for" +msgstr "" + +# Default: Frequency response +msgid "frequency-response" +msgstr "" + +# Default: From +msgid "from" +msgstr "" + +# Default: Full article link +msgid "full-article-link" +msgstr "" + +# Default: Full size cover url +msgid "full-size-cover-url" +msgstr "" + +# Default: Full size headshot +msgid "full-size-headshot" +msgstr "" + +# Default: Genres +msgid "genres" +msgstr "" + +# Default: Goofs +msgid "goofs" +msgstr "" + +# Default: Gross +msgid "gross" +msgstr "" + +# Default: Group genre +msgid "group-genre" +msgstr "" + +# Default: Headshot +msgid "headshot" +msgstr "" + +# Default: Height +msgid "height" +msgstr "" + +# Default: Imdbindex +msgid "imdbindex" +msgstr "" + +# Default: In development +msgid "in-development" +msgstr "" + +# Default: Interview +msgid "interview" +msgstr "" + +# Default: Interviews +msgid "interviews" +msgstr "" + +# Default: Introduction +msgid "introduction" +msgstr "" + +# Default: Item +msgid "item" +msgstr "" + +# Default: Keywords +msgid "keywords" +msgstr "" + +# Default: Kind +msgid "kind" +msgstr "" + +# Default: Label +msgid "label" +msgstr "" + +# Default: Laboratory +msgid "laboratory" +msgstr "" + +# Default: Language +msgid "language" +msgstr "" + +# Default: Languages +msgid "languages" +msgstr "" + +# Default: Laserdisc +msgid "laserdisc" +msgstr "" + +# Default: Laserdisc title +msgid "laserdisc-title" +msgstr "" + +# Default: Length +msgid "length" +msgstr "" + +# Default: Line +msgid "line" +msgstr "" + +# Default: Link +msgid "link" +msgstr "" + +# Default: Link text +msgid "link-text" +msgstr "" + +# Default: Literature +msgid "literature" +msgstr "" + +# Default: Locations +msgid "locations" +msgstr "" + +# Default: Long imdb canonical name +msgid "long-imdb-canonical-name" +msgstr "" + +# Default: Long imdb canonical title +msgid "long-imdb-canonical-title" +msgstr "" + +# Default: Long imdb episode title +msgid "long-imdb-episode-title" +msgstr "" + +# Default: Long imdb name +msgid "long-imdb-name" +msgstr "" + +# Default: Long imdb title +msgid "long-imdb-title" +msgstr "" + +# Default: Magazine cover photo +msgid "magazine-cover-photo" +msgstr "" + +# Default: Make up +msgid "make-up" +msgstr "" + +# Default: Master format +msgid "master-format" +msgstr "" + +# Default: Median +msgid "median" +msgstr "" + +# Default: Merchandising links +msgid "merchandising-links" +msgstr "" + +# Default: Mini biography +msgid "mini-biography" +msgstr "" + +# Default: Misc links +msgid "misc-links" +msgstr "" + +# Default: Miscellaneous companies +msgid "miscellaneous-companies" +msgstr "" + +# Default: Miscellaneous crew +msgid "miscellaneous-crew" +msgstr "" + +# Default: Movie +msgid "movie" +msgstr "" + +# Default: Mpaa +msgid "mpaa" +msgstr "" + +# Default: Music department +msgid "music-department" +msgstr "" + +# Default: Name +msgid "name" +msgstr "" + +# Default: News +msgid "news" +msgstr "" + +# Default: Newsgroup reviews +msgid "newsgroup-reviews" +msgstr "" + +# Default: Nick names +msgid "nick-names" +msgstr "" + +# Default: Notes +msgid "notes" +msgstr "" + +# Default: Novel +msgid "novel" +msgstr "" + +# Default: Number +msgid "number" +msgstr "" + +# Default: Number of chapter stops +msgid "number-of-chapter-stops" +msgstr "" + +# Default: Number of episodes +msgid "number-of-episodes" +msgstr "" + +# Default: Number of seasons +msgid "number-of-seasons" +msgstr "" + +# Default: Number of sides +msgid "number-of-sides" +msgstr "" + +# Default: Number of votes +msgid "number-of-votes" +msgstr "" + +# Default: Official retail price +msgid "official-retail-price" +msgstr "" + +# Default: Official sites +msgid "official-sites" +msgstr "" + +# Default: Opening weekend +msgid "opening-weekend" +msgstr "" + +# Default: Original air date +msgid "original-air-date" +msgstr "" + +# Default: Original music +msgid "original-music" +msgstr "" + +# Default: Original title +msgid "original-title" +msgstr "" + +# Default: Other literature +msgid "other-literature" +msgstr "" + +# Default: Other works +msgid "other-works" +msgstr "" + +# Default: Parents guide +msgid "parents-guide" +msgstr "" + +# Default: Performed by +msgid "performed-by" +msgstr "" + +# Default: Person +msgid "person" +msgstr "" + +# Default: Photo sites +msgid "photo-sites" +msgstr "" + +# Default: Pictorial +msgid "pictorial" +msgstr "" + +# Default: Picture format +msgid "picture-format" +msgstr "" + +# Default: Plot +msgid "plot" +msgstr "" + +# Default: Plot outline +msgid "plot-outline" +msgstr "" + +# Default: Portrayed in +msgid "portrayed-in" +msgstr "" + +# Default: Pressing plant +msgid "pressing-plant" +msgstr "" + +# Default: Printed film format +msgid "printed-film-format" +msgstr "" + +# Default: Printed media reviews +msgid "printed-media-reviews" +msgstr "" + +# Default: Producer +msgid "producer" +msgstr "" + +# Default: Production companies +msgid "production-companies" +msgstr "" + +# Default: Production country +msgid "production-country" +msgstr "" + +# Default: Production dates +msgid "production-dates" +msgstr "" + +# Default: Production design +msgid "production-design" +msgstr "" + +# Default: Production designer +msgid "production-designer" +msgstr "" + +# Default: Production manager +msgid "production-manager" +msgstr "" + +# Default: Production process protocol +msgid "production-process-protocol" +msgstr "" + +# Default: Quality of source +msgid "quality-of-source" +msgstr "" + +# Default: Quality program +msgid "quality-program" +msgstr "" + +# Default: Quote +msgid "quote" +msgstr "" + +# Default: Quotes +msgid "quotes" +msgstr "" + +# Default: Rating +msgid "rating" +msgstr "" + +# Default: Recommendations +msgid "recommendations" +msgstr "" + +# Default: Referenced in +msgid "referenced-in" +msgstr "" + +# Default: References +msgid "references" +msgstr "" + +# Default: Region +msgid "region" +msgstr "" + +# Default: Release country +msgid "release-country" +msgstr "" + +# Default: Release date +msgid "release-date" +msgstr "" + +# Default: Release dates +msgid "release-dates" +msgstr "" + +# Default: Remade as +msgid "remade-as" +msgstr "" + +# Default: Remake of +msgid "remake-of" +msgstr "" + +# Default: Rentals +msgid "rentals" +msgstr "" + +# Default: Result +msgid "result" +msgstr "" + +# Default: Review +msgid "review" +msgstr "" + +# Default: Review author +msgid "review-author" +msgstr "" + +# Default: Review kind +msgid "review-kind" +msgstr "" + +# Default: Runtime +msgid "runtime" +msgstr "" + +# Default: Runtimes +msgid "runtimes" +msgstr "" + +# Default: Salary history +msgid "salary-history" +msgstr "" + +# Default: Screenplay teleplay +msgid "screenplay-teleplay" +msgstr "" + +# Default: Season +msgid "season" +msgstr "" + +# Default: Second unit director or assistant director +msgid "second-unit-director-or-assistant-director" +msgstr "" + +# Default: Self +msgid "self" +msgstr "" + +# Default: Series animation department +msgid "series-animation-department" +msgstr "" + +# Default: Series art department +msgid "series-art-department" +msgstr "" + +# Default: Series assistant directors +msgid "series-assistant-directors" +msgstr "" + +# Default: Series camera department +msgid "series-camera-department" +msgstr "" + +# Default: Series casting department +msgid "series-casting-department" +msgstr "" + +# Default: Series cinematographers +msgid "series-cinematographers" +msgstr "" + +# Default: Series costume department +msgid "series-costume-department" +msgstr "" + +# Default: Series editorial department +msgid "series-editorial-department" +msgstr "" + +# Default: Series editors +msgid "series-editors" +msgstr "" + +# Default: Series make up department +msgid "series-make-up-department" +msgstr "" + +# Default: Series miscellaneous +msgid "series-miscellaneous" +msgstr "" + +# Default: Series music department +msgid "series-music-department" +msgstr "" + +# Default: Series producers +msgid "series-producers" +msgstr "" + +# Default: Series production designers +msgid "series-production-designers" +msgstr "" + +# Default: Series production managers +msgid "series-production-managers" +msgstr "" + +# Default: Series sound department +msgid "series-sound-department" +msgstr "" + +# Default: Series special effects department +msgid "series-special-effects-department" +msgstr "" + +# Default: Series stunts +msgid "series-stunts" +msgstr "" + +# Default: Series title +msgid "series-title" +msgstr "" + +# Default: Series transportation department +msgid "series-transportation-department" +msgstr "" + +# Default: Series visual effects department +msgid "series-visual-effects-department" +msgstr "" + +# Default: Series writers +msgid "series-writers" +msgstr "" + +# Default: Series years +msgid "series-years" +msgstr "" + +# Default: Set decoration +msgid "set-decoration" +msgstr "" + +# Default: Sharpness +msgid "sharpness" +msgstr "" + +# Default: Similar to +msgid "similar-to" +msgstr "" + +# Default: Smart canonical episode title +msgid "smart-canonical-episode-title" +msgstr "" + +# Default: Smart canonical series title +msgid "smart-canonical-series-title" +msgstr "" + +# Default: Smart canonical title +msgid "smart-canonical-title" +msgstr "" + +# Default: Smart long imdb canonical title +msgid "smart-long-imdb-canonical-title" +msgstr "" + +# Default: Sound clips +msgid "sound-clips" +msgstr "" + +# Default: Sound crew +msgid "sound-crew" +msgstr "" + +# Default: Sound encoding +msgid "sound-encoding" +msgstr "" + +# Default: Sound mix +msgid "sound-mix" +msgstr "" + +# Default: Soundtrack +msgid "soundtrack" +msgstr "" + +# Default: Spaciality +msgid "spaciality" +msgstr "" + +# Default: Special effects +msgid "special-effects" +msgstr "" + +# Default: Special effects companies +msgid "special-effects-companies" +msgstr "" + +# Default: Special effects department +msgid "special-effects-department" +msgstr "" + +# Default: Spin off +msgid "spin-off" +msgstr "" + +# Default: Spin off from +msgid "spin-off-from" +msgstr "" + +# Default: Spoofed in +msgid "spoofed-in" +msgstr "" + +# Default: Spoofs +msgid "spoofs" +msgstr "" + +# Default: Spouse +msgid "spouse" +msgstr "" + +# Default: Status of availablility +msgid "status-of-availablility" +msgstr "" + +# Default: Studio +msgid "studio" +msgstr "" + +# Default: Studios +msgid "studios" +msgstr "" + +# Default: Stunt performer +msgid "stunt-performer" +msgstr "" + +# Default: Stunts +msgid "stunts" +msgstr "" + +# Default: Subtitles +msgid "subtitles" +msgstr "" + +# Default: Supplement +msgid "supplement" +msgstr "" + +# Default: Supplements +msgid "supplements" +msgstr "" + +# Default: Synopsis +msgid "synopsis" +msgstr "" + +# Default: Taglines +msgid "taglines" +msgstr "" + +# Default: Tech info +msgid "tech-info" +msgstr "" + +# Default: Thanks +msgid "thanks" +msgstr "" + +# Default: Time +msgid "time" +msgstr "" + +# Default: Title +msgid "title" +msgstr "" + +# Default: Titles in this product +msgid "titles-in-this-product" +msgstr "" + +# Default: To +msgid "to" +msgstr "" + +# Default: Top 250 rank +msgid "top-250-rank" +msgstr "" + +# Default: Trade mark +msgid "trade-mark" +msgstr "" + +# Default: Transportation department +msgid "transportation-department" +msgstr "" + +# Default: Trivia +msgid "trivia" +msgstr "" + +# Default: Tv +msgid "tv" +msgstr "" + +# Default: Under license from +msgid "under-license-from" +msgstr "" + +# Default: Unknown link +msgid "unknown-link" +msgstr "" + +# Default: Upc +msgid "upc" +msgstr "" + +# Default: Version of +msgid "version-of" +msgstr "" + +# Default: Vhs +msgid "vhs" +msgstr "" + +# Default: Video +msgid "video" +msgstr "" + +# Default: Video artifacts +msgid "video-artifacts" +msgstr "" + +# Default: Video clips +msgid "video-clips" +msgstr "" + +# Default: Video noise +msgid "video-noise" +msgstr "" + +# Default: Video quality +msgid "video-quality" +msgstr "" + +# Default: Video standard +msgid "video-standard" +msgstr "" + +# Default: Visual effects +msgid "visual-effects" +msgstr "" + +# Default: Votes +msgid "votes" +msgstr "" + +# Default: Votes distribution +msgid "votes-distribution" +msgstr "" + +# Default: Weekend gross +msgid "weekend-gross" +msgstr "" + +# Default: Where now +msgid "where-now" +msgstr "" + +# Default: With +msgid "with" +msgstr "" + +# Default: Writer +msgid "writer" +msgstr "" + +# Default: Written by +msgid "written-by" +msgstr "" + +# Default: Year +msgid "year" +msgstr "" + +# Default: Zshops +msgid "zshops" +msgstr "" + diff --git a/libs/imdb/locale/rebuildmo.py b/libs/imdb/locale/rebuildmo.py old mode 100644 new mode 100755 diff --git a/libs/imdb/parser/http/__init__.py b/libs/imdb/parser/http/__init__.py index ade424fd..e05d9afa 100644 --- a/libs/imdb/parser/http/__init__.py +++ b/libs/imdb/parser/http/__init__.py @@ -109,6 +109,10 @@ _old_cookie_uu = '3M3AXsquTU5Gur/Svik+ewflPm5Rk2ieY3BIPlLjyK3C0Dp9F8UoPgbTyKiGtZ _cookie_id = 'rH1jNAkjTlNXvHolvBVBsgaPICNZbNdjVjzFwzas9JRmusdjVoqBs/Hs12NR+1WFxEoR9bGKEDUg6sNlADqXwkas12N131Rwdb+UQNGKN8PWrNdjcdqBQVLq8mbGDHP3hqzxhbD692NQi9D0JjpBtRaPIbP1zNdjUOqENQYv1ADWrNcT9vyXU1' _cookie_uu = 'su4/m8cho4c6HP+W1qgq6wchOmhnF0w+lIWvHjRUPJ6nRA9sccEafjGADJ6hQGrMd4GKqLcz2X4z5+w+M4OIKnRn7FpENH7dxDQu3bQEHyx0ZEyeRFTPHfQEX03XF+yeN1dsPpcXaqjUZAw+lGRfXRQEfz3RIX9IgVEffdBAHw2wQXyf9xdMPrQELw0QNB8dsffsqcdQemjPB0w+moLcPh0JrKrHJ9hjBzdMPpcXTH7XRwwOk=' +# imdbpy2010 account. +#_cookie_id = 'QrCdxVi+L+WgqOLrQJJgBgRRXGInphxiBPU/YXSFDyExMFzCp6YcYgSVXyEUhS/xMID8wqemHGID4DlntwZ49vemP5UXsAxiJ4D6goSmHGIgNT9hMXBaRSF2vMS3phxB0bVfQiQlP1RxdrzhB6YcRHFASyIhQVowwXCKtDSlD2YhgRvxBsCKtGemHBKH9mxSI=' +#_cookie_uu = 'oiEo2yoJFCA2Zbn/o7Z1LAPIwotAu6QdALv3foDb1x5F/tdrFY63XkSfty4kntS8Y8jkHSDLt3406+d+JThEilPI0mtTaOQdA/t2/iErp22jaLdeVU5ya4PIREpj7HFdpzhEHadcIAngSER50IoHDpD6Bz4Qy3b+UIhE/hBbhz5Q63ceA2hEvhPo5B0FnrL9Q8jkWjDIbA0Au3d+AOtnXoCIRL4Q28c+UOtnXpP4RL4T6OQdA+6ijUCI5B0AW2d+UOtnXpPYRL4T6OQdA8jkTUOYlC0A==' + class _FakeURLOpener(object): """Fake URLOpener object, used to return empty strings instead of diff --git a/libs/imdb/parser/http/movieParser.py b/libs/imdb/parser/http/movieParser.py index 89a6e8cd..524eda40 100644 --- a/libs/imdb/parser/http/movieParser.py +++ b/libs/imdb/parser/http/movieParser.py @@ -225,8 +225,8 @@ class DOMHTMLMovieParser(DOMParserBase): postprocess=lambda x: x.strip()), Attribute(key="countries", path="./h5[starts-with(text(), " \ - "'Countr')]/..//a/text()", - postprocess=makeSplitter(sep='\n')), + "'Countr')]/../div[@class='info-content']//text()", + postprocess=makeSplitter('|')), Attribute(key="language", path="./h5[starts-with(text(), " \ "'Language')]/..//text()", @@ -541,11 +541,13 @@ class DOMHTMLPlotParser(DOMParserBase): def _process_award(x): award = {} + award['award'] = x.get('award').strip() + if not award['award']: + return {} award['year'] = x.get('year').strip() if award['year'] and award['year'].isdigit(): award['year'] = int(award['year']) award['result'] = x.get('result').strip() - award['award'] = x.get('award').strip() category = x.get('category').strip() if category: award['category'] = category @@ -649,6 +651,8 @@ class DOMHTMLAwardsParser(DOMParserBase): assigner = self.xpath(dom, "//a/text()")[0] for entry in data[key]: if not entry.has_key('name'): + if not entry: + continue # this is an award, not a recipient entry['assigner'] = assigner.strip() # find the recipients @@ -996,8 +1000,10 @@ class DOMHTMLRatingsParser(DOMParserBase): if votes: nd['number of votes'] = {} for i in xrange(1, 11): - nd['number of votes'][int(votes[i]['ordinal'])] = \ - int(votes[i]['votes'].replace(',', '')) + _ordinal = int(votes[i]['ordinal']) + _strvts = votes[i]['votes'] or '0' + nd['number of votes'][_ordinal] = \ + int(_strvts.replace(',', '')) mean = data.get('mean and median', '') if mean: means = self.re_means.findall(mean) @@ -1699,10 +1705,14 @@ class DOMHTMLEpisodesParser(DOMParserBase): try: season_key = int(season_key) except: pass nd[season_key] = {} + ep_counter = 1 for episode in data[key]: if not episode: continue episode_key = episode.get('episode') if episode_key is None: continue + if not isinstance(episode_key, int): + episode_key = ep_counter + ep_counter += 1 cast_key = 'Season %s, Episode %s:' % (season_key, episode_key) if data.has_key(cast_key): diff --git a/libs/imdb/parser/http/personParser.py b/libs/imdb/parser/http/personParser.py index d35f378e..a5fbc081 100644 --- a/libs/imdb/parser/http/personParser.py +++ b/libs/imdb/parser/http/personParser.py @@ -63,85 +63,131 @@ class DOMHTMLMaindetailsParser(DOMParserBase): _birth_attrs = [Attribute(key='birth date', path={ - 'day': "./div/a[starts-with(@href, " \ + 'day': ".//a[starts-with(@href, " \ "'/date/')]/text()", - 'year': "./div/a[starts-with(@href, " \ + 'year': ".//a[starts-with(@href, " \ "'/search/name?birth_year=')]/text()" }, postprocess=build_date), - Attribute(key='birth notes', - path="./div/a[starts-with(@href, " \ + Attribute(key='birth place', + path=".//a[starts-with(@href, " \ "'/search/name?birth_place=')]/text()")] _death_attrs = [Attribute(key='death date', path={ - 'day': "./div/a[starts-with(@href, " \ + 'day': ".//a[starts-with(@href, " \ "'/date/')]/text()", - 'year': "./div/a[starts-with(@href, " \ - "'/search/name?death_date=')]/text()" + 'year': ".//a[starts-with(@href, " \ + "'/search/name?death_year=')]/text()" }, postprocess=build_date), - Attribute(key='death notes', - path="./div/text()", - # TODO: check if this slicing is always correct - postprocess=lambda x: x.strip()[2:])] + Attribute(key='death place', + path=".//a[starts-with(@href, " \ + "'/search/name?death_place=')]/text()")] _film_attrs = [Attribute(key=None, multi=True, path={ - 'link': "./a[1]/@href", - 'title': ".//text()", - 'status': "./i/a//text()", - 'roleID': "./div[@class='_imdbpyrole']/@roleid" + 'link': "./b/a[1]/@href", + 'title': "./b/a[1]/text()", + 'notes': "./b/following-sibling::text()", + 'year': "./span[@class='year_column']/text()", + 'status': "./a[@class='in_production']/text()", + 'rolesNoChar': './/br/following-sibling::text()', + 'chrRoles': "./a[@imdbpyname]/@imdbpyname", + 'roleID': "./a[starts-with(@href, '/character/')]/@href" }, postprocess=lambda x: build_movie(x.get('title') or u'', + year=x.get('year'), movieID=analyze_imdbid(x.get('link') or u''), - roleID=(x.get('roleID') or u'').split('/'), + rolesNoChar=(x.get('rolesNoChar') or u'').strip(), + chrRoles=(x.get('chrRoles') or u'').strip(), + additionalNotes=x.get('notes'), + roleID=(x.get('roleID') or u''), status=x.get('status') or None))] extractors = [ - Extractor(label='page title', - path="//title", + Extractor(label='name', + path="//h1[@class='header']", attrs=Attribute(key='name', - path="./text()", + path=".//text()", postprocess=lambda x: analyze_name(x, - canonical=1))), + canonical=1))), Extractor(label='birth info', - path="//div[h5='Date of Birth:']", + path="//div[h4='Born:']", attrs=_birth_attrs), Extractor(label='death info', - path="//div[h5='Date of Death:']", + path="//div[h4='Died:']", attrs=_death_attrs), Extractor(label='headshot', - path="//a[@name='headshot']", + path="//td[@id='img_primary']/a", attrs=Attribute(key='headshot', path="./img/@src")), Extractor(label='akas', - path="//div[h5='Alternate Names:']", + path="//div[h4='Alternate Names:']", attrs=Attribute(key='akas', - path="./div/text()", - postprocess=lambda x: x.strip().split(' | '))), + path="./text()", + postprocess=lambda x: x.strip().split(' '))), Extractor(label='filmography', - group="//div[@class='filmo'][h5]", - group_key="./h5/a[@name]/text()", - group_key_normalize=lambda x: x.lower()[:-1], - path="./ol/li", - attrs=_film_attrs) + group="//div[starts-with(@id, 'filmo-head-')]", + group_key="./a[@name]/text()", + group_key_normalize=lambda x: x.lower().replace(': ', ' '), + path="./following-sibling::div[1]" \ + "/div[starts-with(@class, 'filmo-row')]", + attrs=_film_attrs), + + Extractor(label='indevelopment', + path="//div[starts-with(@class,'devitem')]", + attrs=Attribute(key='in development', + multi=True, + path={ + 'link': './a/@href', + 'title': './a/text()' + }, + postprocess=lambda x: + build_movie(x.get('title') or u'', + movieID=analyze_imdbid(x.get('link') or u''), + roleID=(x.get('roleID') or u'').split('/'), + status=x.get('status') or None))) ] - preprocessors = [ - # XXX: check that this doesn't cut "status" or other info... - (re.compile(r'
(\.\.\.| ?).+?', re.I | re.M | re.S), - ''), - (_reRoles, _manageRoles)] + + preprocessors = [('
', ''), + ('
', '
'), + (re.compile(r'((.*?)'), + r'\1 imdbpyname="\2@@">\2')] def postprocess_data(self, data): for what in 'birth date', 'death date': if what in data and not data[what]: del data[what] + # XXX: the code below is for backwards compatibility + # probably could be removed + for key in data.keys(): + if key.startswith('actor '): + if not data.has_key('actor'): + data['actor'] = [] + data['actor'].extend(data[key]) + del data[key] + if key.startswith('actress '): + if not data.has_key('actress'): + data['actress'] = [] + data['actress'].extend(data[key]) + del data[key] + if key.startswith('self '): + if not data.has_key('self'): + data['self'] = [] + data['self'].extend(data[key]) + del data[key] + if key == 'birth place': + data['birth notes'] = data[key] + del data[key] + if key == 'death place': + data['death notes'] = data[key] + del data[key] return data @@ -181,6 +227,10 @@ class DOMHTMLBioParser(DOMParserBase): # TODO: check if this slicing is always correct postprocess=lambda x: u''.join(x).strip()[2:])] extractors = [ + Extractor(label='headshot', + path="//a[@name='headshot']", + attrs=Attribute(key='headshot', + path="./img/@src")), Extractor(label='birth info', path="//div[h5='Date of Birth']", attrs=_birth_attrs), diff --git a/libs/imdb/parser/http/utils.py b/libs/imdb/parser/http/utils.py index 2e6c9114..eeca3b07 100644 --- a/libs/imdb/parser/http/utils.py +++ b/libs/imdb/parser/http/utils.py @@ -262,14 +262,20 @@ def build_person(txt, personID=None, billingPos=None, return person +_re_chrIDs = re.compile('[0-9]{7}') + _b_m_logger = logging.getLogger('imdbpy.parser.http.build_movie') # To shrink spaces. re_spaces = re.compile(r'\s+') def build_movie(txt, movieID=None, roleID=None, status=None, accessSystem='http', modFunct=None, _parsingCharacter=False, - _parsingCompany=False): + _parsingCompany=False, year=None, chrRoles=None, + rolesNoChar=None, additionalNotes=None): """Given a string as normally seen on the "categorized" page of a person on the IMDb's web site, returns a Movie instance.""" + # FIXME: Oook, lets face it: build_movie and build_person are now + # two horrible sets of patches to support the new IMDb design. They + # must be rewritten from scratch. if _parsingCharacter: _defSep = ' Played by ' elif _parsingCompany: @@ -291,6 +297,8 @@ def build_movie(txt, movieID=None, roleID=None, status=None, title = title[:-14] + ' (mini)' # Try to understand where the movie title ends. while True: + if year: + break if title[-1:] != ')': # Ignore the silly "TV Series" notice. if title[-9:] == 'TV Series': @@ -319,12 +327,24 @@ def build_movie(txt, movieID=None, roleID=None, status=None, if notes: notes = '%s %s' % (title[nidx:], notes) else: notes = title[nidx:] title = title[:nidx].rstrip() + if year: + year = year.strip() + if title[-1] == ')': + fpIdx = title.rfind('(') + if fpIdx != -1: + if notes: notes = '%s %s' % (title[fpIdx:], notes) + else: notes = title[fpIdx:] + title = title[:fpIdx].rstrip() + title = u'%s (%s)' % (title, year) if _parsingCharacter and roleID and not role: roleID = None if not roleID: roleID = None elif len(roleID) == 1: roleID = roleID[0] + if not role and chrRoles and isinstance(roleID, (str, unicode)): + roleID = _re_chrIDs.findall(roleID) + role = ' / '.join(filter(None, chrRoles.split('@@'))) # Manages multiple roleIDs. if isinstance(roleID, list): tmprole = role.split('/') @@ -355,13 +375,29 @@ def build_movie(txt, movieID=None, roleID=None, status=None, movieID = str(movieID) if (not title) or (movieID is None): _b_m_logger.error('empty title or movieID for "%s"', txt) + if rolesNoChar: + rolesNoChar = filter(None, [x.strip() for x in rolesNoChar.split('/')]) + if not role: + role = [] + elif not isinstance(role, list): + role = [role] + role += rolesNoChar + notes = notes.strip() + if additionalNotes: + additionalNotes = re_spaces.sub(' ', additionalNotes).strip() + if notes: + notes += u' ' + notes += additionalNotes m = Movie(title=title, movieID=movieID, notes=notes, currentRole=role, roleID=roleID, roleIsPerson=_parsingCharacter, modFunct=modFunct, accessSystem=accessSystem) if roleNotes and len(roleNotes) == len(roleID): for idx, role in enumerate(m.currentRole): - if roleNotes[idx]: - role.notes = roleNotes[idx] + try: + if roleNotes[idx]: + role.notes = roleNotes[idx] + except IndexError: + break # Status can't be checked here, and must be detected by the parser. if status: m['status'] = status @@ -468,8 +504,10 @@ class DOMParserBase(object): # converted to title=""Family Guy"" and this confuses BeautifulSoup. if self.usingModule == 'beautifulsoup': html_string = html_string.replace('""', '"') + #print html_string.encode('utf8') if html_string: dom = self.get_dom(html_string) + #print self.tostring(dom).encode('utf8') try: dom = self.preprocess_dom(dom) except Exception, e: diff --git a/libs/imdb/parser/mobile/__init__.py b/libs/imdb/parser/mobile/__init__.py index e486aa24..ce623ec8 100644 --- a/libs/imdb/parser/mobile/__init__.py +++ b/libs/imdb/parser/mobile/__init__.py @@ -52,6 +52,10 @@ re_imdbID = re.compile(r'(?<=nm|tt|ch)([0-9]{7})\b') # movie AKAs. re_makas = re.compile('(

.*?

)') +# Remove episode numbers. +re_filmo_episodes = re.compile('
.*?
', + re.M | re.I) + def _unHtml(s): """Return a string without tags and no multiple spaces.""" @@ -537,24 +541,33 @@ class IMDbMobileAccessSystem(IMDbHTTPAccessSystem): if _parseChr: w = 'characterID' else: w = 'personID' raise IMDbDataAccessError, 'unable to get %s "%s"' % (w, personID) - name = _unHtml(name[0]) + name = _unHtml(name[0].replace(' - IMDb', '')) if _parseChr: name = name.replace('(Character)', '').strip() name = name.replace('- Filmography by type', '').strip() else: name = name.replace('- Filmography by', '').strip() r = analyze_name(name, canonical=not _parseChr) - for dKind in ('birth', 'death'): - date = _findBetween(s, '
Date of %s:
' % dKind.capitalize(), - ('', '

'), maxRes=1) + for dKind in ('Born', 'Died'): + date = _findBetween(s, '%s:' % dKind.capitalize(), + ('
', '

'), maxRes=1) if date: date = _unHtml(date[0]) if date: - date, notes = date_and_notes(date) + #date, notes = date_and_notes(date) + # TODO: fix to handle real names. + date_notes = date.split(' in ', 1) + notes = u'' + date = date_notes[0] + if len(date_notes) == 2: + notes = date_notes[1] + dtitle = 'birth' + if dKind == 'Died': + dtitle = 'death' if date: - r['%s date' % dKind] = date + r['%s date' % dtitle] = date if notes: - r['%s notes' % dKind] = notes + r['%s notes' % dtitle] = notes akas = _findBetween(s, 'Alternate Names:', ('
', '

'), maxRes=1) if akas: @@ -569,18 +582,13 @@ class IMDbMobileAccessSystem(IMDbHTTPAccessSystem): hs[:] = _findBetween(hs[0], 'src="', '"', maxRes=1) if hs: r['headshot'] = hs[0] # Build a list of tuples such [('hrefLink', 'section name')] - workkind = _findBetween(s, '
', '
', - maxRes=1) - if workkind: - workkind[:] = _findBetween(workkind[0], 'href="#', '
') - else: - # Assume there's only one section and/or there are no - # section links, for some reason. - workkind[:] = _findBetween(s, '
') - workkind[:] = [x.lstrip('"').rstrip(':').lower() for x in workkind] + workkind = _findBetween(s, 'id="jumpto_', '') ws = [] for work in workkind: - wsplit = work.split('">', 1) + sep = '" >' + if '">' in work: + sep = '">' + wsplit = work.split(sep, 1) if len(wsplit) == 2: sect = wsplit[0] if '"' in sect: @@ -600,16 +608,22 @@ class IMDbMobileAccessSystem(IMDbHTTPAccessSystem): else: inisect = s.find('', ('', '
', '
')) + mlist = _findBetween(raws, '
',)) for m in mlist: + fCB = m.find('>') + if fCB != -1: + m = m[fCB+1:].lstrip() + m = re_filmo_episodes.sub('', m) # For every movie in the current section. movieID = re_imdbID.findall(m) if not movieID: self._mobile_logger.debug('no movieID in %s', m) continue + m = m.replace('
', ' .... ', 1) if not _parseChr: chrIndx = m.find(' .... ') else: @@ -638,14 +652,22 @@ class IMDbMobileAccessSystem(IMDbHTTPAccessSystem): if stendidx != -1: status = _unHtml(m[stidx+3:stendidx]) m = m.replace(m[stidx+3:stendidx], '') + year = _findBetween(m, 'year_column">', '', maxRes=1) + if year: + year = year[0] + m = m.replace('%s' % year, + '') + else: + year = None m = _unHtml(m) if not m: - self._mobile_logger.warn('no title fo rmovieID %s', movieID) + self._mobile_logger.warn('no title for movieID %s', movieID) continue movie = build_movie(m, movieID=movieID, status=status, roleID=chids, modFunct=self._defModFunct, accessSystem=self.accessSystem, - _parsingCharacter=_parseChr) + _parsingCharacter=_parseChr, year=year) + sectName = sectName.split(':')[0] r.setdefault(sectName, []).append(movie) # If available, take the always correct name from a form. itag = _getTagsWith(s, 'NAME="primary"', maxRes=1) diff --git a/libs/imdb/parser/sql/__init__.py b/libs/imdb/parser/sql/__init__.py new file mode 100644 index 00000000..22510e6c --- /dev/null +++ b/libs/imdb/parser/sql/__init__.py @@ -0,0 +1,1589 @@ +""" +parser.sql package (imdb package). + +This package provides the IMDbSqlAccessSystem class used to access +IMDb's data through a SQL database. Every database supported by +the SQLObject _AND_ SQLAlchemy Object Relational Managers is available. +the imdb.IMDb function will return an instance of this class when +called with the 'accessSystem' argument set to "sql", "database" or "db". + +Copyright 2005-2010 Davide Alberani + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +# FIXME: this whole module was written in a veeery short amount of time. +# The code should be commented, rewritten and cleaned. :-) + +import re +import logging +from difflib import SequenceMatcher +from codecs import lookup + +from imdb import IMDbBase +from imdb.utils import normalizeName, normalizeTitle, build_title, \ + build_name, analyze_name, analyze_title, \ + canonicalTitle, canonicalName, re_titleRef, \ + build_company_name, re_episodes, _unicodeArticles, \ + analyze_company_name, re_year_index, re_nameRef +from imdb.Person import Person +from imdb.Movie import Movie +from imdb.Company import Company +from imdb._exceptions import IMDbDataAccessError, IMDbError + + +# Logger for miscellaneous functions. +_aux_logger = logging.getLogger('imdbpy.parser.sql.aux') + +# ============================= +# Things that once upon a time were in imdb.parser.common.locsql. + +def titleVariations(title, fromPtdf=0): + """Build title variations useful for searches; if fromPtdf is true, + the input is assumed to be in the plain text data files format.""" + if fromPtdf: title1 = u'' + else: title1 = title + title2 = title3 = u'' + if fromPtdf or re_year_index.search(title): + # If it appears to have a (year[/imdbIndex]) indication, + # assume that a long imdb canonical name was provided. + titldict = analyze_title(title, canonical=1) + # title1: the canonical name. + title1 = titldict['title'] + if titldict['kind'] != 'episode': + # title3: the long imdb canonical name. + if fromPtdf: title3 = title + else: title3 = build_title(titldict, canonical=1, ptdf=1) + else: + title1 = normalizeTitle(title1) + title3 = build_title(titldict, canonical=1, ptdf=1) + else: + # Just a title. + # title1: the canonical title. + title1 = canonicalTitle(title) + title3 = u'' + # title2 is title1 without the article, or title1 unchanged. + if title1: + title2 = title1 + t2s = title2.split(u', ') + if t2s[-1].lower() in _unicodeArticles: + title2 = u', '.join(t2s[:-1]) + _aux_logger.debug('title variations: 1:[%s] 2:[%s] 3:[%s]', + title1, title2, title3) + return title1, title2, title3 + + +re_nameIndex = re.compile(r'\(([IVXLCDM]+)\)') + +def nameVariations(name, fromPtdf=0): + """Build name variations useful for searches; if fromPtdf is true, + the input is assumed to be in the plain text data files format.""" + name1 = name2 = name3 = u'' + if fromPtdf or re_nameIndex.search(name): + # We've a name with an (imdbIndex) + namedict = analyze_name(name, canonical=1) + # name1 is the name in the canonical format. + name1 = namedict['name'] + # name3 is the canonical name with the imdbIndex. + if fromPtdf: + if namedict.has_key('imdbIndex'): + name3 = name + else: + name3 = build_name(namedict, canonical=1) + else: + # name1 is the name in the canonical format. + name1 = canonicalName(name) + name3 = u'' + # name2 is the name in the normal format, if it differs from name1. + name2 = normalizeName(name1) + if name1 == name2: name2 = u'' + _aux_logger.debug('name variations: 1:[%s] 2:[%s] 3:[%s]', + name1, name2, name3) + return name1, name2, name3 + + +try: + from cutils import ratcliff as _ratcliff + def ratcliff(s1, s2, sm): + """Return the Ratcliff-Obershelp value between the two strings, + using the C implementation.""" + return _ratcliff(s1.encode('latin_1', 'replace'), + s2.encode('latin_1', 'replace')) +except ImportError: + _aux_logger.warn('Unable to import the cutils.ratcliff function.' + ' Searching names and titles using the "sql"' + ' data access system will be slower.') + + def ratcliff(s1, s2, sm): + """Ratcliff-Obershelp similarity.""" + STRING_MAXLENDIFFER = 0.7 + s1len = len(s1) + s2len = len(s2) + if s1len < s2len: + threshold = float(s1len) / s2len + else: + threshold = float(s2len) / s1len + if threshold < STRING_MAXLENDIFFER: + return 0.0 + sm.set_seq2(s2.lower()) + return sm.ratio() + + +def merge_roles(mop): + """Merge multiple roles.""" + new_list = [] + for m in mop: + if m in new_list: + keep_this = new_list[new_list.index(m)] + if not isinstance(keep_this.currentRole, list): + keep_this.currentRole = [keep_this.currentRole] + keep_this.currentRole.append(m.currentRole) + else: + new_list.append(m) + return new_list + + +def scan_names(name_list, name1, name2, name3, results=0, ro_thresold=None, + _scan_character=False): + """Scan a list of names, searching for best matches against + the given variations.""" + if ro_thresold is not None: RO_THRESHOLD = ro_thresold + else: RO_THRESHOLD = 0.6 + sm1 = SequenceMatcher() + sm2 = SequenceMatcher() + sm3 = SequenceMatcher() + sm1.set_seq1(name1.lower()) + if name2: sm2.set_seq1(name2.lower()) + if name3: sm3.set_seq1(name3.lower()) + resd = {} + for i, n_data in name_list: + nil = n_data['name'] + # XXX: on Symbian, here we get a str; not sure this is the + # right place to fix it. + if isinstance(nil, str): + nil = unicode(nil, 'latin1', 'ignore') + # Distance with the canonical name. + ratios = [ratcliff(name1, nil, sm1) + 0.05] + namesurname = u'' + if not _scan_character: + nils = nil.split(', ', 1) + surname = nils[0] + if len(nils) == 2: namesurname = '%s %s' % (nils[1], surname) + else: + nils = nil.split(' ', 1) + surname = nils[-1] + namesurname = nil + if surname != nil: + # Distance with the "Surname" in the database. + ratios.append(ratcliff(name1, surname, sm1)) + if not _scan_character: + ratios.append(ratcliff(name1, namesurname, sm1)) + if name2: + ratios.append(ratcliff(name2, surname, sm2)) + # Distance with the "Name Surname" in the database. + if namesurname: + ratios.append(ratcliff(name2, namesurname, sm2)) + if name3: + # Distance with the long imdb canonical name. + ratios.append(ratcliff(name3, + build_name(n_data, canonical=1), sm3) + 0.1) + ratio = max(ratios) + if ratio >= RO_THRESHOLD: + if resd.has_key(i): + if ratio > resd[i][0]: resd[i] = (ratio, (i, n_data)) + else: resd[i] = (ratio, (i, n_data)) + res = resd.values() + res.sort() + res.reverse() + if results > 0: res[:] = res[:results] + return res + + +def scan_titles(titles_list, title1, title2, title3, results=0, + searchingEpisode=0, onlyEpisodes=0, ro_thresold=None): + """Scan a list of titles, searching for best matches against + the given variations.""" + if ro_thresold is not None: RO_THRESHOLD = ro_thresold + else: RO_THRESHOLD = 0.6 + sm1 = SequenceMatcher() + sm2 = SequenceMatcher() + sm3 = SequenceMatcher() + sm1.set_seq1(title1.lower()) + sm2.set_seq2(title2.lower()) + if title3: + sm3.set_seq1(title3.lower()) + if title3[-1] == '}': searchingEpisode = 1 + hasArt = 0 + if title2 != title1: hasArt = 1 + resd = {} + for i, t_data in titles_list: + if onlyEpisodes: + if t_data.get('kind') != 'episode': + continue + til = t_data['title'] + if til[-1] == ')': + dateIdx = til.rfind('(') + if dateIdx != -1: + til = til[:dateIdx].rstrip() + if not til: + continue + ratio = ratcliff(title1, til, sm1) + if ratio >= RO_THRESHOLD: + resd[i] = (ratio, (i, t_data)) + continue + if searchingEpisode: + if t_data.get('kind') != 'episode': continue + elif t_data.get('kind') == 'episode': continue + til = t_data['title'] + # XXX: on Symbian, here we get a str; not sure this is the + # right place to fix it. + if isinstance(til, str): + til = unicode(til, 'latin1', 'ignore') + # Distance with the canonical title (with or without article). + # titleS -> titleR + # titleS, the -> titleR, the + if not searchingEpisode: + til = canonicalTitle(til) + ratios = [ratcliff(title1, til, sm1) + 0.05] + # til2 is til without the article, if present. + til2 = til + tils = til2.split(', ') + matchHasArt = 0 + if tils[-1].lower() in _unicodeArticles: + til2 = ', '.join(tils[:-1]) + matchHasArt = 1 + if hasArt and not matchHasArt: + # titleS[, the] -> titleR + ratios.append(ratcliff(title2, til, sm2)) + elif matchHasArt and not hasArt: + # titleS -> titleR[, the] + ratios.append(ratcliff(title1, til2, sm1)) + else: + ratios = [0.0] + if title3: + # Distance with the long imdb canonical title. + ratios.append(ratcliff(title3, + build_title(t_data, canonical=1, ptdf=1), sm3) + 0.1) + ratio = max(ratios) + if ratio >= RO_THRESHOLD: + if resd.has_key(i): + if ratio > resd[i][0]: + resd[i] = (ratio, (i, t_data)) + else: resd[i] = (ratio, (i, t_data)) + res = resd.values() + res.sort() + res.reverse() + if results > 0: res[:] = res[:results] + return res + + +def scan_company_names(name_list, name1, results=0, ro_thresold=None): + """Scan a list of company names, searching for best matches against + the given name. Notice that this function takes a list of + strings, and not a list of dictionaries.""" + if ro_thresold is not None: RO_THRESHOLD = ro_thresold + else: RO_THRESHOLD = 0.6 + sm1 = SequenceMatcher() + sm1.set_seq1(name1.lower()) + resd = {} + withoutCountry = not name1.endswith(']') + for i, n in name_list: + # XXX: on Symbian, here we get a str; not sure this is the + # right place to fix it. + if isinstance(n, str): + n = unicode(n, 'latin1', 'ignore') + o_name = n + var = 0.0 + if withoutCountry and n.endswith(']'): + cidx = n.rfind('[') + if cidx != -1: + n = n[:cidx].rstrip() + var = -0.05 + # Distance with the company name. + ratio = ratcliff(name1, n, sm1) + var + if ratio >= RO_THRESHOLD: + if resd.has_key(i): + if ratio > resd[i][0]: resd[i] = (ratio, + (i, analyze_company_name(o_name))) + else: + resd[i] = (ratio, (i, analyze_company_name(o_name))) + res = resd.values() + res.sort() + res.reverse() + if results > 0: res[:] = res[:results] + return res + + +try: + from cutils import soundex +except ImportError: + _aux_logger.warn('Unable to import the cutils.soundex function.' + ' Searches of movie titles and person names will be' + ' a bit slower.') + + _translate = dict(B='1', C='2', D='3', F='1', G='2', J='2', K='2', L='4', + M='5', N='5', P='1', Q='2', R='6', S='2', T='3', V='1', + X='2', Z='2') + _translateget = _translate.get + _re_non_ascii = re.compile(r'^[^a-z]*', re.I) + SOUNDEX_LEN = 5 + + def soundex(s): + """Return the soundex code for the given string.""" + # Maximum length of the soundex code. + s = _re_non_ascii.sub('', s) + if not s: return None + s = s.upper() + soundCode = s[0] + for c in s[1:]: + cw = _translateget(c, '0') + if cw != '0' and soundCode[-1] != cw: + soundCode += cw + return soundCode[:SOUNDEX_LEN] or None + + +def _sortKeywords(keyword, kwds): + """Sort a list of keywords, based on the searched one.""" + sm = SequenceMatcher() + sm.set_seq1(keyword.lower()) + ratios = [(ratcliff(keyword, k, sm), k) for k in kwds] + checkContained = False + if len(keyword) > 4: + checkContained = True + for idx, data in enumerate(ratios): + ratio, key = data + if key.startswith(keyword): + ratios[idx] = (ratio+0.5, key) + elif checkContained and keyword in key: + ratios[idx] = (ratio+0.3, key) + ratios.sort() + ratios.reverse() + return [r[1] for r in ratios] + + +def filterSimilarKeywords(keyword, kwdsIterator): + """Return a sorted list of keywords similar to the one given.""" + seenDict = {} + kwdSndx = soundex(keyword.encode('ascii', 'ignore')) + matches = [] + matchesappend = matches.append + checkContained = False + if len(keyword) > 4: + checkContained = True + for movieID, key in kwdsIterator: + if key in seenDict: + continue + seenDict[key] = None + if checkContained and keyword in key: + matchesappend(key) + continue + if kwdSndx == soundex(key.encode('ascii', 'ignore')): + matchesappend(key) + return _sortKeywords(keyword, matches) + + + +# ============================= + +_litlist = ['screenplay/teleplay', 'novel', 'adaption', 'book', + 'production process protocol', 'interviews', + 'printed media reviews', 'essays', 'other literature'] +_litd = dict([(x, ('literature', x)) for x in _litlist]) + +_buslist = ['budget', 'weekend gross', 'gross', 'opening weekend', 'rentals', + 'admissions', 'filming dates', 'production dates', 'studios', + 'copyright holder'] +_busd = dict([(x, ('business', x)) for x in _buslist]) + + +def _reGroupDict(d, newgr): + """Regroup keys in the d dictionary in subdictionaries, based on + the scheme in the newgr dictionary. + E.g.: in the newgr, an entry 'LD label': ('laserdisc', 'label') + tells the _reGroupDict() function to take the entry with + label 'LD label' (as received from the sql database) + and put it in the subsection (another dictionary) named + 'laserdisc', using the key 'label'.""" + r = {} + newgrks = newgr.keys() + for k, v in d.items(): + if k in newgrks: + r.setdefault(newgr[k][0], {})[newgr[k][1]] = v + # A not-so-clearer version: + ##r.setdefault(newgr[k][0], {}) + ##r[newgr[k][0]][newgr[k][1]] = v + else: r[k] = v + return r + + +def _groupListBy(l, index): + """Regroup items in a list in a list of lists, grouped by + the value at the given index.""" + tmpd = {} + for item in l: + tmpd.setdefault(item[index], []).append(item) + res = tmpd.values() + return res + + +def sub_dict(d, keys): + """Return the subdictionary of 'd', with just the keys listed in 'keys'.""" + return dict([(k, d[k]) for k in keys if k in d]) + + +def get_movie_data(movieID, kindDict, fromAka=0, _table=None): + """Return a dictionary containing data about the given movieID; + if fromAka is true, the AkaTitle table is searched; _table is + reserved for the imdbpy2sql.py script.""" + if _table is not None: + Table = _table + else: + if not fromAka: Table = Title + else: Table = AkaTitle + m = Table.get(movieID) + mdict = {'title': m.title, 'kind': kindDict[m.kindID], + 'year': m.productionYear, 'imdbIndex': m.imdbIndex, + 'season': m.seasonNr, 'episode': m.episodeNr} + if not fromAka: + if m.seriesYears is not None: + mdict['series years'] = unicode(m.seriesYears) + if mdict['imdbIndex'] is None: del mdict['imdbIndex'] + if mdict['year'] is None: del mdict['year'] + else: + try: + mdict['year'] = int(mdict['year']) + except (TypeError, ValueError): + del mdict['year'] + if mdict['season'] is None: del mdict['season'] + else: + try: mdict['season'] = int(mdict['season']) + except: pass + if mdict['episode'] is None: del mdict['episode'] + else: + try: mdict['episode'] = int(mdict['episode']) + except: pass + episodeOfID = m.episodeOfID + if episodeOfID is not None: + ser_dict = get_movie_data(episodeOfID, kindDict, fromAka) + mdict['episode of'] = Movie(data=ser_dict, movieID=episodeOfID, + accessSystem='sql') + if fromAka: + ser_note = AkaTitle.get(episodeOfID).note + if ser_note: + mdict['episode of'].notes = ser_note + return mdict + + +def _iterKeywords(results): + """Iterate over (key.id, key.keyword) columns of a selection of + the Keyword table.""" + for key in results: + yield key.id, key.keyword + + +def getSingleInfo(table, movieID, infoType, notAList=False): + """Return a dictionary in the form {infoType: infoListOrString}, + retrieving a single set of information about a given movie, from + the specified table.""" + infoTypeID = InfoType.select(InfoType.q.info == infoType) + if infoTypeID.count() == 0: + return {} + res = table.select(AND(table.q.movieID == movieID, + table.q.infoTypeID == infoTypeID[0].id)) + retList = [] + for r in res: + info = r.info + note = r.note + if note: + info += u'::%s' % note + retList.append(info) + if not retList: + return {} + if not notAList: return {infoType: retList} + else: return {infoType: retList[0]} + + +def _cmpTop(a, b, what='top 250 rank'): + """Compare function used to sort top 250/bottom 10 rank.""" + av = int(a[1].get(what)) + bv = int(b[1].get(what)) + if av == bv: + return 0 + return (-1, 1)[av > bv] + +def _cmpBottom(a, b): + """Compare function used to sort top 250/bottom 10 rank.""" + return _cmpTop(a, b, what='bottom 10 rank') + + +class IMDbSqlAccessSystem(IMDbBase): + """The class used to access IMDb's data through a SQL database.""" + + accessSystem = 'sql' + _sql_logger = logging.getLogger('imdbpy.parser.sql') + + def __init__(self, uri, adultSearch=1, useORM=None, *arguments, **keywords): + """Initialize the access system.""" + IMDbBase.__init__(self, *arguments, **keywords) + if useORM is None: + useORM = ('sqlobject', 'sqlalchemy') + if not isinstance(useORM, (tuple, list)): + if ',' in useORM: + useORM = useORM.split(',') + else: + useORM = [useORM] + self.useORM = useORM + nrMods = len(useORM) + _gotError = False + DB_TABLES = [] + for idx, mod in enumerate(useORM): + mod = mod.strip().lower() + try: + if mod == 'sqlalchemy': + from alchemyadapter import getDBTables, NotFoundError, \ + setConnection, AND, OR, IN, \ + ISNULL, CONTAINSSTRING, toUTF8 + elif mod == 'sqlobject': + from objectadapter import getDBTables, NotFoundError, \ + setConnection, AND, OR, IN, \ + ISNULL, CONTAINSSTRING, toUTF8 + else: + self._sql_logger.warn('unknown module "%s"' % mod) + continue + self._sql_logger.info('using %s ORM', mod) + # XXX: look ma'... black magic! It's used to make + # TableClasses and some functions accessible + # through the whole module. + for k, v in [('NotFoundError', NotFoundError), + ('AND', AND), ('OR', OR), ('IN', IN), + ('ISNULL', ISNULL), + ('CONTAINSSTRING', CONTAINSSTRING)]: + globals()[k] = v + self.toUTF8 = toUTF8 + DB_TABLES = getDBTables(uri) + for t in DB_TABLES: + globals()[t._imdbpyName] = t + if _gotError: + self._sql_logger.warn('falling back to "%s"' % mod) + break + except ImportError, e: + if idx+1 >= nrMods: + raise IMDbError, 'unable to use any ORM in %s: %s' % ( + str(useORM), str(e)) + else: + self._sql_logger.warn('unable to use "%s": %s' % (mod, + str(e))) + _gotError = True + continue + else: + raise IMDbError, 'unable to use any ORM in %s' % str(useORM) + # Set the connection to the database. + self._sql_logger.debug('connecting to %s', uri) + try: + self._connection = setConnection(uri, DB_TABLES) + except AssertionError, e: + raise IMDbDataAccessError, \ + 'unable to connect to the database server; ' + \ + 'complete message: "%s"' % str(e) + self.Error = self._connection.module.Error + # Maps some IDs to the corresponding strings. + self._kind = {} + self._kindRev = {} + self._sql_logger.debug('reading constants from the database') + try: + for kt in KindType.select(): + self._kind[kt.id] = kt.kind + self._kindRev[str(kt.kind)] = kt.id + except self.Error: + # NOTE: you can also get the error, but - at least with + # MySQL - it also contains the password, and I don't + # like the idea to print it out. + raise IMDbDataAccessError, \ + 'unable to connect to the database server' + self._role = {} + for rl in RoleType.select(): + self._role[rl.id] = str(rl.role) + self._info = {} + self._infoRev = {} + for inf in InfoType.select(): + self._info[inf.id] = str(inf.info) + self._infoRev[str(inf.info)] = inf.id + self._compType = {} + for cType in CompanyType.select(): + self._compType[cType.id] = cType.kind + info = [(it.id, it.info) for it in InfoType.select()] + self._compcast = {} + for cc in CompCastType.select(): + self._compcast[cc.id] = str(cc.kind) + self._link = {} + for lt in LinkType.select(): + self._link[lt.id] = str(lt.link) + self._moviesubs = {} + # Build self._moviesubs, a dictionary used to rearrange + # the data structure for a movie object. + for vid, vinfo in info: + if not vinfo.startswith('LD '): continue + self._moviesubs[vinfo] = ('laserdisc', vinfo[3:]) + self._moviesubs.update(_litd) + self._moviesubs.update(_busd) + self.do_adult_search(adultSearch) + + def _findRefs(self, o, trefs, nrefs): + """Find titles or names references in strings.""" + if isinstance(o, (unicode, str)): + for title in re_titleRef.findall(o): + a_title = analyze_title(title, canonical=0) + rtitle = build_title(a_title, ptdf=1) + if trefs.has_key(rtitle): continue + movieID = self._getTitleID(rtitle) + if movieID is None: + movieID = self._getTitleID(title) + if movieID is None: + continue + m = Movie(title=rtitle, movieID=movieID, + accessSystem=self.accessSystem) + trefs[rtitle] = m + rtitle2 = canonicalTitle(a_title.get('title', u'')) + if rtitle2 and rtitle2 != rtitle and rtitle2 != title: + trefs[rtitle2] = m + if title != rtitle: + trefs[title] = m + for name in re_nameRef.findall(o): + a_name = analyze_name(name, canonical=1) + rname = build_name(a_name, canonical=1) + if nrefs.has_key(rname): continue + personID = self._getNameID(rname) + if personID is None: + personID = self._getNameID(name) + if personID is None: continue + p = Person(name=rname, personID=personID, + accessSystem=self.accessSystem) + nrefs[rname] = p + rname2 = normalizeName(a_name.get('name', u'')) + if rname2 and rname2 != rname: + nrefs[rname2] = p + if name != rname and name != rname2: + nrefs[name] = p + elif isinstance(o, (list, tuple)): + for item in o: + self._findRefs(item, trefs, nrefs) + elif isinstance(o, dict): + for value in o.values(): + self._findRefs(value, trefs, nrefs) + return (trefs, nrefs) + + def _extractRefs(self, o): + """Scan for titles or names references in strings.""" + trefs = {} + nrefs = {} + try: + return self._findRefs(o, trefs, nrefs) + except RuntimeError, e: + # Symbian/python 2.2 has a poor regexp implementation. + import warnings + warnings.warn('RuntimeError in ' + "imdb.parser.sql.IMDbSqlAccessSystem; " + "if it's not a recursion limit exceeded and we're not " + "running in a Symbian environment, it's a bug:\n%s" % e) + return (trefs, nrefs) + + def _changeAKAencoding(self, akanotes, akatitle): + """Return akatitle in the correct charset, as specified in + the akanotes field; if akatitle doesn't need to be modified, + return None.""" + oti = akanotes.find('(original ') + if oti == -1: return None + ote = akanotes[oti+10:].find(' title)') + if ote != -1: + cs_info = akanotes[oti+10:oti+10+ote].lower().split() + for e in cs_info: + # excludes some strings that clearly are not encoding. + if e in ('script', '', 'cyrillic', 'greek'): continue + if e.startswith('iso-') and e.find('latin') != -1: + e = e[4:].replace('-', '') + try: + lookup(e) + lat1 = akatitle.encode('latin_1', 'replace') + return unicode(lat1, e, 'replace') + except (LookupError, ValueError, TypeError): + continue + return None + + def _buildNULLCondition(self, col, val): + """Build a comparison for columns where values can be NULL.""" + if val is None: + return ISNULL(col) + else: + if isinstance(val, (int, long)): + return col == val + else: + return col == self.toUTF8(val) + + def _getTitleID(self, title): + """Given a long imdb canonical title, returns a movieID or + None if not found.""" + td = analyze_title(title) + condition = None + if td['kind'] == 'episode': + epof = td['episode of'] + seriesID = [s.id for s in Title.select( + AND(Title.q.title == self.toUTF8(epof['title']), + self._buildNULLCondition(Title.q.imdbIndex, + epof.get('imdbIndex')), + Title.q.kindID == self._kindRev[epof['kind']], + self._buildNULLCondition(Title.q.productionYear, + epof.get('year'))))] + if seriesID: + condition = AND(IN(Title.q.episodeOfID, seriesID), + Title.q.title == self.toUTF8(td['title']), + self._buildNULLCondition(Title.q.imdbIndex, + td.get('imdbIndex')), + Title.q.kindID == self._kindRev[td['kind']], + self._buildNULLCondition(Title.q.productionYear, + td.get('year'))) + if condition is None: + condition = AND(Title.q.title == self.toUTF8(td['title']), + self._buildNULLCondition(Title.q.imdbIndex, + td.get('imdbIndex')), + Title.q.kindID == self._kindRev[td['kind']], + self._buildNULLCondition(Title.q.productionYear, + td.get('year'))) + res = Title.select(condition) + try: + if res.count() != 1: + return None + except (UnicodeDecodeError, TypeError): + return None + return res[0].id + + def _getNameID(self, name): + """Given a long imdb canonical name, returns a personID or + None if not found.""" + nd = analyze_name(name) + res = Name.select(AND(Name.q.name == self.toUTF8(nd['name']), + self._buildNULLCondition(Name.q.imdbIndex, + nd.get('imdbIndex')))) + try: + c = res.count() + if res.count() != 1: + return None + except (UnicodeDecodeError, TypeError): + return None + return res[0].id + + def _normalize_movieID(self, movieID): + """Normalize the given movieID.""" + try: + return int(movieID) + except (ValueError, OverflowError): + raise IMDbError, 'movieID "%s" can\'t be converted to integer' % \ + movieID + + def _normalize_personID(self, personID): + """Normalize the given personID.""" + try: + return int(personID) + except (ValueError, OverflowError): + raise IMDbError, 'personID "%s" can\'t be converted to integer' % \ + personID + + def _normalize_characterID(self, characterID): + """Normalize the given characterID.""" + try: + return int(characterID) + except (ValueError, OverflowError): + raise IMDbError, 'characterID "%s" can\'t be converted to integer' \ + % characterID + + def _normalize_companyID(self, companyID): + """Normalize the given companyID.""" + try: + return int(companyID) + except (ValueError, OverflowError): + raise IMDbError, 'companyID "%s" can\'t be converted to integer' \ + % companyID + + def get_imdbMovieID(self, movieID): + """Translate a movieID in an imdbID. + If not in the database, try an Exact Primary Title search on IMDb; + return None if it's unable to get the imdbID. + """ + try: movie = Title.get(movieID) + except NotFoundError: return None + imdbID = movie.imdbID + if imdbID is not None: return '%07d' % imdbID + m_dict = get_movie_data(movie.id, self._kind) + titline = build_title(m_dict, ptdf=1) + imdbID = self.title2imdbID(titline) + # If the imdbID was retrieved from the web and was not in the + # database, update the database (ignoring errors, because it's + # possibile that the current user has not update privileges). + # There're times when I think I'm a genius; this one of + # those times... + if imdbID is not None: + try: movie.imdbID = int(imdbID) + except: pass + return imdbID + + def get_imdbPersonID(self, personID): + """Translate a personID in an imdbID. + If not in the database, try an Exact Primary Name search on IMDb; + return None if it's unable to get the imdbID. + """ + try: person = Name.get(personID) + except NotFoundError: return None + imdbID = person.imdbID + if imdbID is not None: return '%07d' % imdbID + n_dict = {'name': person.name, 'imdbIndex': person.imdbIndex} + namline = build_name(n_dict, canonical=1) + imdbID = self.name2imdbID(namline) + if imdbID is not None: + try: person.imdbID = int(imdbID) + except: pass + return imdbID + + def get_imdbCharacterID(self, characterID): + """Translate a characterID in an imdbID. + If not in the database, try an Exact Primary Name search on IMDb; + return None if it's unable to get the imdbID. + """ + try: character = CharName.get(characterID) + except NotFoundError: return None + imdbID = character.imdbID + if imdbID is not None: return '%07d' % imdbID + n_dict = {'name': character.name, 'imdbIndex': character.imdbIndex} + namline = build_name(n_dict, canonical=1) + imdbID = self.character2imdbID(namline) + if imdbID is not None: + try: character.imdbID = int(imdbID) + except: pass + return imdbID + + def get_imdbCompanyID(self, companyID): + """Translate a companyID in an imdbID. + If not in the database, try an Exact Primary Name search on IMDb; + return None if it's unable to get the imdbID. + """ + try: company = CompanyName.get(companyID) + except NotFoundError: return None + imdbID = company.imdbID + if imdbID is not None: return '%07d' % imdbID + n_dict = {'name': company.name, 'country': company.countryCode} + namline = build_company_name(n_dict) + imdbID = self.company2imdbID(namline) + if imdbID is not None: + try: company.imdbID = int(imdbID) + except: pass + return imdbID + + def do_adult_search(self, doAdult): + """If set to 0 or False, movies in the Adult category are not + episodeOf = title_dict.get('episode of') + shown in the results of a search.""" + self.doAdult = doAdult + + def _search_movie(self, title, results, _episodes=False): + title = title.strip() + if not title: return [] + title_dict = analyze_title(title, canonical=1) + s_title = title_dict['title'] + if not s_title: return [] + episodeOf = title_dict.get('episode of') + if episodeOf: + _episodes = False + s_title_split = s_title.split(', ') + if len(s_title_split) > 1 and \ + s_title_split[-1].lower() in _unicodeArticles: + s_title_rebuilt = ', '.join(s_title_split[:-1]) + if s_title_rebuilt: + s_title = s_title_rebuilt + #if not episodeOf: + # if not _episodes: + # s_title_split = s_title.split(', ') + # if len(s_title_split) > 1 and \ + # s_title_split[-1].lower() in _articles: + # s_title_rebuilt = ', '.join(s_title_split[:-1]) + # if s_title_rebuilt: + # s_title = s_title_rebuilt + #else: + # _episodes = False + if isinstance(s_title, unicode): + s_title = s_title.encode('ascii', 'ignore') + + soundexCode = soundex(s_title) + + # XXX: improve the search restricting the kindID if the + # "kind" of the input differs from "movie"? + condition = conditionAka = None + if _episodes: + condition = AND(Title.q.phoneticCode == soundexCode, + Title.q.kindID == self._kindRev['episode']) + conditionAka = AND(AkaTitle.q.phoneticCode == soundexCode, + AkaTitle.q.kindID == self._kindRev['episode']) + elif title_dict['kind'] == 'episode' and episodeOf is not None: + # set canonical=0 ? Should not make much difference. + series_title = build_title(episodeOf, canonical=1) + # XXX: is it safe to get "results" results? + # Too many? Too few? + serRes = results + if serRes < 3 or serRes > 10: + serRes = 10 + searchSeries = self._search_movie(series_title, serRes) + seriesIDs = [result[0] for result in searchSeries] + if seriesIDs: + condition = AND(Title.q.phoneticCode == soundexCode, + IN(Title.q.episodeOfID, seriesIDs), + Title.q.kindID == self._kindRev['episode']) + conditionAka = AND(AkaTitle.q.phoneticCode == soundexCode, + IN(AkaTitle.q.episodeOfID, seriesIDs), + AkaTitle.q.kindID == self._kindRev['episode']) + else: + # XXX: bad situation: we have found no matching series; + # try searching everything (both episodes and + # non-episodes) for the title. + condition = AND(Title.q.phoneticCode == soundexCode, + IN(Title.q.episodeOfID, seriesIDs)) + conditionAka = AND(AkaTitle.q.phoneticCode == soundexCode, + IN(AkaTitle.q.episodeOfID, seriesIDs)) + if condition is None: + # XXX: excludes episodes? + condition = AND(Title.q.kindID != self._kindRev['episode'], + Title.q.phoneticCode == soundexCode) + conditionAka = AND(AkaTitle.q.kindID != self._kindRev['episode'], + AkaTitle.q.phoneticCode == soundexCode) + + # Up to 3 variations of the title are searched, plus the + # long imdb canonical title, if provided. + if not _episodes: + title1, title2, title3 = titleVariations(title) + else: + title1 = title + title2 = '' + title3 = '' + try: + qr = [(q.id, get_movie_data(q.id, self._kind)) + for q in Title.select(condition)] + q2 = [(q.movieID, get_movie_data(q.id, self._kind, fromAka=1)) + for q in AkaTitle.select(conditionAka)] + qr += q2 + except NotFoundError, e: + raise IMDbDataAccessError, \ + 'unable to search the database: "%s"' % str(e) + + resultsST = results * 3 + res = scan_titles(qr, title1, title2, title3, resultsST, + searchingEpisode=episodeOf is not None, + onlyEpisodes=_episodes, + ro_thresold=0.0) + res[:] = [x[1] for x in res] + + if res and not self.doAdult: + mids = [x[0] for x in res] + genreID = self._infoRev['genres'] + adultlist = [al.movieID for al + in MovieInfo.select( + AND(MovieInfo.q.infoTypeID == genreID, + MovieInfo.q.info == 'Adult', + IN(MovieInfo.q.movieID, mids)))] + res[:] = [x for x in res if x[0] not in adultlist] + + new_res = [] + # XXX: can there be duplicates? + for r in res: + if r not in q2: + new_res.append(r) + continue + mdict = r[1] + aka_title = build_title(mdict, ptdf=1) + orig_dict = get_movie_data(r[0], self._kind) + orig_title = build_title(orig_dict, ptdf=1) + if aka_title == orig_title: + new_res.append(r) + continue + orig_dict['akas'] = [aka_title] + new_res.append((r[0], orig_dict)) + if results > 0: new_res[:] = new_res[:results] + return new_res + + def _search_episode(self, title, results): + return self._search_movie(title, results, _episodes=True) + + def get_movie_main(self, movieID): + # Every movie information is retrieved from here. + infosets = self.get_movie_infoset() + try: + res = get_movie_data(movieID, self._kind) + except NotFoundError, e: + raise IMDbDataAccessError, \ + 'unable to get movieID "%s": "%s"' % (movieID, str(e)) + if not res: + raise IMDbDataAccessError, 'unable to get movieID "%s"' % movieID + # Collect cast information. + castdata = [[cd.personID, cd.personRoleID, cd.note, cd.nrOrder, + self._role[cd.roleID]] + for cd in CastInfo.select(CastInfo.q.movieID == movieID)] + for p in castdata: + person = Name.get(p[0]) + p += [person.name, person.imdbIndex] + if p[4] in ('actor', 'actress'): + p[4] = 'cast' + # Regroup by role/duty (cast, writer, director, ...) + castdata[:] = _groupListBy(castdata, 4) + for group in castdata: + duty = group[0][4] + for pdata in group: + curRole = pdata[1] + curRoleID = None + if curRole is not None: + robj = CharName.get(curRole) + curRole = robj.name + curRoleID = robj.id + p = Person(personID=pdata[0], name=pdata[5], + currentRole=curRole or u'', + roleID=curRoleID, + notes=pdata[2] or u'', + accessSystem='sql') + if pdata[6]: p['imdbIndex'] = pdata[6] + p.billingPos = pdata[3] + res.setdefault(duty, []).append(p) + if duty == 'cast': + res[duty] = merge_roles(res[duty]) + res[duty].sort() + # Info about the movie. + minfo = [(self._info[m.infoTypeID], m.info, m.note) + for m in MovieInfo.select(MovieInfo.q.movieID == movieID)] + minfo += [(self._info[m.infoTypeID], m.info, m.note) + for m in MovieInfoIdx.select(MovieInfoIdx.q.movieID == movieID)] + minfo += [('keywords', Keyword.get(m.keywordID).keyword, None) + for m in MovieKeyword.select(MovieKeyword.q.movieID == movieID)] + minfo = _groupListBy(minfo, 0) + for group in minfo: + sect = group[0][0] + for mdata in group: + data = mdata[1] + if mdata[2]: data += '::%s' % mdata[2] + res.setdefault(sect, []).append(data) + # Companies info about a movie. + cinfo = [(self._compType[m.companyTypeID], m.companyID, m.note) for m + in MovieCompanies.select(MovieCompanies.q.movieID == movieID)] + cinfo = _groupListBy(cinfo, 0) + for group in cinfo: + sect = group[0][0] + for mdata in group: + cDb = CompanyName.get(mdata[1]) + cDbTxt = cDb.name + if cDb.countryCode: + cDbTxt += ' %s' % cDb.countryCode + company = Company(name=cDbTxt, + companyID=mdata[1], + notes=mdata[2] or u'', + accessSystem=self.accessSystem) + res.setdefault(sect, []).append(company) + # AKA titles. + akat = [(get_movie_data(at.id, self._kind, fromAka=1), at.note) + for at in AkaTitle.select(AkaTitle.q.movieID == movieID)] + if akat: + res['akas'] = [] + for td, note in akat: + nt = build_title(td, ptdf=1) + if note: + net = self._changeAKAencoding(note, nt) + if net is not None: nt = net + nt += '::%s' % note + if nt not in res['akas']: res['akas'].append(nt) + # Complete cast/crew. + compcast = [(self._compcast[cc.subjectID], self._compcast[cc.statusID]) + for cc in CompleteCast.select(CompleteCast.q.movieID == movieID)] + if compcast: + for entry in compcast: + val = unicode(entry[1]) + res[u'complete %s' % entry[0]] = val + # Movie connections. + mlinks = [[ml.linkedMovieID, self._link[ml.linkTypeID]] + for ml in MovieLink.select(MovieLink.q.movieID == movieID)] + if mlinks: + for ml in mlinks: + lmovieData = get_movie_data(ml[0], self._kind) + m = Movie(movieID=ml[0], data=lmovieData, accessSystem='sql') + ml[0] = m + res['connections'] = {} + mlinks[:] = _groupListBy(mlinks, 1) + for group in mlinks: + lt = group[0][1] + res['connections'][lt] = [i[0] for i in group] + # Episodes. + episodes = {} + eps_list = list(Title.select(Title.q.episodeOfID == movieID)) + eps_list.sort() + if eps_list: + ps_data = {'title': res['title'], 'kind': res['kind'], + 'year': res.get('year'), + 'imdbIndex': res.get('imdbIndex')} + parentSeries = Movie(movieID=movieID, data=ps_data, + accessSystem='sql') + for episode in eps_list: + episodeID = episode.id + episode_data = get_movie_data(episodeID, self._kind) + m = Movie(movieID=episodeID, data=episode_data, + accessSystem='sql') + m['episode of'] = parentSeries + season = episode_data.get('season', 'UNKNOWN') + if season not in episodes: episodes[season] = {} + ep_number = episode_data.get('episode') + if ep_number is None: + ep_number = max((episodes[season].keys() or [0])) + 1 + episodes[season][ep_number] = m + res['episodes'] = episodes + res['number of episodes'] = sum([len(x) for x in episodes.values()]) + res['number of seasons'] = len(episodes.keys()) + # Regroup laserdisc information. + res = _reGroupDict(res, self._moviesubs) + # Do some transformation to preserve consistency with other + # data access systems. + if 'quotes' in res: + for idx, quote in enumerate(res['quotes']): + res['quotes'][idx] = quote.split('::') + if 'runtimes' in res and len(res['runtimes']) > 0: + rt = res['runtimes'][0] + episodes = re_episodes.findall(rt) + if episodes: + res['runtimes'][0] = re_episodes.sub('', rt) + if res['runtimes'][0][-2:] == '::': + res['runtimes'][0] = res['runtimes'][0][:-2] + if 'votes' in res: + res['votes'] = int(res['votes'][0]) + if 'rating' in res: + res['rating'] = float(res['rating'][0]) + if 'votes distribution' in res: + res['votes distribution'] = res['votes distribution'][0] + if 'mpaa' in res: + res['mpaa'] = res['mpaa'][0] + if 'top 250 rank' in res: + try: res['top 250 rank'] = int(res['top 250 rank']) + except: pass + if 'bottom 10 rank' in res: + try: res['bottom 100 rank'] = int(res['bottom 10 rank']) + except: pass + del res['bottom 10 rank'] + for old, new in [('guest', 'guests'), ('trademarks', 'trade-mark'), + ('articles', 'article'), ('pictorials', 'pictorial'), + ('magazine-covers', 'magazine-cover-photo')]: + if old in res: + res[new] = res[old] + del res[old] + trefs,nrefs = {}, {} + trefs,nrefs = self._extractRefs(sub_dict(res,Movie.keys_tomodify_list)) + return {'data': res, 'titlesRefs': trefs, 'namesRefs': nrefs, + 'info sets': infosets} + + # Just to know what kind of information are available. + get_movie_alternate_versions = get_movie_main + get_movie_business = get_movie_main + get_movie_connections = get_movie_main + get_movie_crazy_credits = get_movie_main + get_movie_goofs = get_movie_main + get_movie_keywords = get_movie_main + get_movie_literature = get_movie_main + get_movie_locations = get_movie_main + get_movie_plot = get_movie_main + get_movie_quotes = get_movie_main + get_movie_release_dates = get_movie_main + get_movie_soundtrack = get_movie_main + get_movie_taglines = get_movie_main + get_movie_technical = get_movie_main + get_movie_trivia = get_movie_main + get_movie_vote_details = get_movie_main + get_movie_episodes = get_movie_main + + def _search_person(self, name, results): + name = name.strip() + if not name: return [] + s_name = analyze_name(name)['name'] + if not s_name: return [] + if isinstance(s_name, unicode): + s_name = s_name.encode('ascii', 'ignore') + soundexCode = soundex(s_name) + name1, name2, name3 = nameVariations(name) + + # If the soundex is None, compare only with the first + # phoneticCode column. + if soundexCode is not None: + condition = IN(soundexCode, [Name.q.namePcodeCf, + Name.q.namePcodeNf, + Name.q.surnamePcode]) + conditionAka = IN(soundexCode, [AkaName.q.namePcodeCf, + AkaName.q.namePcodeNf, + AkaName.q.surnamePcode]) + else: + condition = ISNULL(Name.q.namePcodeCf) + conditionAka = ISNULL(AkaName.q.namePcodeCf) + + try: + qr = [(q.id, {'name': q.name, 'imdbIndex': q.imdbIndex}) + for q in Name.select(condition)] + + q2 = [(q.personID, {'name': q.name, 'imdbIndex': q.imdbIndex}) + for q in AkaName.select(conditionAka)] + qr += q2 + except NotFoundError, e: + raise IMDbDataAccessError, \ + 'unable to search the database: "%s"' % str(e) + + res = scan_names(qr, name1, name2, name3, results) + res[:] = [x[1] for x in res] + # Purge empty imdbIndex. + returnl = [] + for x in res: + tmpd = x[1] + if tmpd['imdbIndex'] is None: + del tmpd['imdbIndex'] + returnl.append((x[0], tmpd)) + + new_res = [] + # XXX: can there be duplicates? + for r in returnl: + if r not in q2: + new_res.append(r) + continue + pdict = r[1] + aka_name = build_name(pdict, canonical=1) + p = Name.get(r[0]) + orig_dict = {'name': p.name, 'imdbIndex': p.imdbIndex} + if orig_dict['imdbIndex'] is None: + del orig_dict['imdbIndex'] + orig_name = build_name(orig_dict, canonical=1) + if aka_name == orig_name: + new_res.append(r) + continue + orig_dict['akas'] = [aka_name] + new_res.append((r[0], orig_dict)) + if results > 0: new_res[:] = new_res[:results] + + return new_res + + def get_person_main(self, personID): + # Every person information is retrieved from here. + infosets = self.get_person_infoset() + try: + p = Name.get(personID) + except NotFoundError, e: + raise IMDbDataAccessError, \ + 'unable to get personID "%s": "%s"' % (personID, str(e)) + res = {'name': p.name, 'imdbIndex': p.imdbIndex} + if res['imdbIndex'] is None: del res['imdbIndex'] + if not res: + raise IMDbDataAccessError, 'unable to get personID "%s"' % personID + # Collect cast information. + castdata = [(cd.movieID, cd.personRoleID, cd.note, + self._role[cd.roleID], + get_movie_data(cd.movieID, self._kind)) + for cd in CastInfo.select(CastInfo.q.personID == personID)] + # Regroup by role/duty (cast, writer, director, ...) + castdata[:] = _groupListBy(castdata, 3) + episodes = {} + seenDuties = [] + for group in castdata: + for mdata in group: + duty = orig_duty = group[0][3] + if duty not in seenDuties: seenDuties.append(orig_duty) + note = mdata[2] or u'' + if 'episode of' in mdata[4]: + duty = 'episodes' + if orig_duty not in ('actor', 'actress'): + if note: note = ' %s' % note + note = '[%s]%s' % (orig_duty, note) + curRole = mdata[1] + curRoleID = None + if curRole is not None: + robj = CharName.get(curRole) + curRole = robj.name + curRoleID = robj.id + m = Movie(movieID=mdata[0], data=mdata[4], + currentRole=curRole or u'', + roleID=curRoleID, + notes=note, accessSystem='sql') + if duty != 'episodes': + res.setdefault(duty, []).append(m) + else: + episodes.setdefault(m['episode of'], []).append(m) + if episodes: + for k in episodes: + episodes[k].sort() + episodes[k].reverse() + res['episodes'] = episodes + for duty in seenDuties: + if duty in res: + if duty in ('actor', 'actress', 'himself', 'herself', + 'themselves'): + res[duty] = merge_roles(res[duty]) + res[duty].sort() + # Info about the person. + pinfo = [(self._info[pi.infoTypeID], pi.info, pi.note) + for pi in PersonInfo.select(PersonInfo.q.personID == personID)] + # Regroup by duty. + pinfo = _groupListBy(pinfo, 0) + for group in pinfo: + sect = group[0][0] + for pdata in group: + data = pdata[1] + if pdata[2]: data += '::%s' % pdata[2] + res.setdefault(sect, []).append(data) + # AKA names. + akan = [(an.name, an.imdbIndex) + for an in AkaName.select(AkaName.q.personID == personID)] + if akan: + res['akas'] = [] + for n in akan: + nd = {'name': n[0]} + if n[1]: nd['imdbIndex'] = n[1] + nt = build_name(nd, canonical=1) + res['akas'].append(nt) + # Do some transformation to preserve consistency with other + # data access systems. + for key in ('birth date', 'birth notes', 'death date', 'death notes', + 'birth name', 'height'): + if key in res: + res[key] = res[key][0] + if 'guest' in res: + res['notable tv guest appearances'] = res['guest'] + del res['guest'] + miscnames = res.get('nick names', []) + if 'birth name' in res: miscnames.append(res['birth name']) + if 'akas' in res: + for mname in miscnames: + if mname in res['akas']: res['akas'].remove(mname) + if not res['akas']: del res['akas'] + trefs,nrefs = self._extractRefs(sub_dict(res,Person.keys_tomodify_list)) + return {'data': res, 'titlesRefs': trefs, 'namesRefs': nrefs, + 'info sets': infosets} + + # Just to know what kind of information are available. + get_person_filmography = get_person_main + get_person_biography = get_person_main + get_person_other_works = get_person_main + get_person_episodes = get_person_main + + def _search_character(self, name, results): + name = name.strip() + if not name: return [] + s_name = analyze_name(name)['name'] + if not s_name: return [] + if isinstance(s_name, unicode): + s_name = s_name.encode('ascii', 'ignore') + s_name = normalizeName(s_name) + soundexCode = soundex(s_name) + surname = s_name.split(' ')[-1] + surnameSoundex = soundex(surname) + name2 = '' + soundexName2 = None + nsplit = s_name.split() + if len(nsplit) > 1: + name2 = '%s %s' % (nsplit[-1], ' '.join(nsplit[:-1])) + if s_name == name2: + name2 = '' + else: + soundexName2 = soundex(name2) + # If the soundex is None, compare only with the first + # phoneticCode column. + if soundexCode is not None: + if soundexName2 is not None: + condition = OR(surnameSoundex == CharName.q.surnamePcode, + IN(CharName.q.namePcodeNf, [soundexCode, + soundexName2]), + IN(CharName.q.surnamePcode, [soundexCode, + soundexName2])) + else: + condition = OR(surnameSoundex == CharName.q.surnamePcode, + IN(soundexCode, [CharName.q.namePcodeNf, + CharName.q.surnamePcode])) + else: + condition = ISNULL(Name.q.namePcodeNf) + try: + qr = [(q.id, {'name': q.name, 'imdbIndex': q.imdbIndex}) + for q in CharName.select(condition)] + except NotFoundError, e: + raise IMDbDataAccessError, \ + 'unable to search the database: "%s"' % str(e) + res = scan_names(qr, s_name, name2, '', results, + _scan_character=True) + res[:] = [x[1] for x in res] + # Purge empty imdbIndex. + returnl = [] + for x in res: + tmpd = x[1] + if tmpd['imdbIndex'] is None: + del tmpd['imdbIndex'] + returnl.append((x[0], tmpd)) + return returnl + + def get_character_main(self, characterID, results=1000): + # Every character information is retrieved from here. + infosets = self.get_character_infoset() + try: + c = CharName.get(characterID) + except NotFoundError, e: + raise IMDbDataAccessError, \ + 'unable to get characterID "%s": "%s"' % (characterID, e) + res = {'name': c.name, 'imdbIndex': c.imdbIndex} + if res['imdbIndex'] is None: del res['imdbIndex'] + if not res: + raise IMDbDataAccessError, 'unable to get characterID "%s"' % \ + characterID + # Collect filmography information. + items = CastInfo.select(CastInfo.q.personRoleID == characterID) + if results > 0: + items = items[:results] + filmodata = [(cd.movieID, cd.personID, cd.note, + get_movie_data(cd.movieID, self._kind)) for cd in items + if self._role[cd.roleID] in ('actor', 'actress')] + fdata = [] + for f in filmodata: + curRole = None + curRoleID = f[1] + note = f[2] or u'' + if curRoleID is not None: + robj = Name.get(curRoleID) + curRole = robj.name + m = Movie(movieID=f[0], data=f[3], + currentRole=curRole or u'', + roleID=curRoleID, roleIsPerson=True, + notes=note, accessSystem='sql') + fdata.append(m) + fdata = merge_roles(fdata) + fdata.sort() + if fdata: + res['filmography'] = fdata + return {'data': res, 'info sets': infosets} + + get_character_filmography = get_character_main + get_character_biography = get_character_main + + def _search_company(self, name, results): + name = name.strip() + if not name: return [] + if isinstance(name, unicode): + name = name.encode('ascii', 'ignore') + soundexCode = soundex(name) + # If the soundex is None, compare only with the first + # phoneticCode column. + if soundexCode is None: + condition = ISNULL(CompanyName.q.namePcodeNf) + else: + if name.endswith(']'): + condition = CompanyName.q.namePcodeSf == soundexCode + else: + condition = CompanyName.q.namePcodeNf == soundexCode + try: + qr = [(q.id, {'name': q.name, 'country': q.countryCode}) + for q in CompanyName.select(condition)] + except NotFoundError, e: + raise IMDbDataAccessError, \ + 'unable to search the database: "%s"' % str(e) + qr[:] = [(x[0], build_company_name(x[1])) for x in qr] + res = scan_company_names(qr, name, results) + res[:] = [x[1] for x in res] + # Purge empty country keys. + returnl = [] + for x in res: + tmpd = x[1] + country = tmpd.get('country') + if country is None and 'country' in tmpd: + del tmpd['country'] + returnl.append((x[0], tmpd)) + return returnl + + def get_company_main(self, companyID, results=0): + # Every company information is retrieved from here. + infosets = self.get_company_infoset() + try: + c = CompanyName.get(companyID) + except NotFoundError, e: + raise IMDbDataAccessError, \ + 'unable to get companyID "%s": "%s"' % (companyID, e) + res = {'name': c.name, 'country': c.countryCode} + if res['country'] is None: del res['country'] + if not res: + raise IMDbDataAccessError, 'unable to get companyID "%s"' % \ + companyID + # Collect filmography information. + items = MovieCompanies.select(MovieCompanies.q.companyID == companyID) + if results > 0: + items = items[:results] + filmodata = [(cd.movieID, cd.companyID, + self._compType[cd.companyTypeID], cd.note, + get_movie_data(cd.movieID, self._kind)) for cd in items] + filmodata = _groupListBy(filmodata, 2) + for group in filmodata: + ctype = group[0][2] + for movieID, companyID, ctype, note, movieData in group: + movie = Movie(data=movieData, movieID=movieID, + notes=note or u'', accessSystem=self.accessSystem) + res.setdefault(ctype, []).append(movie) + res.get(ctype, []).sort() + return {'data': res, 'info sets': infosets} + + def _search_keyword(self, keyword, results): + constr = OR(Keyword.q.phoneticCode == + soundex(keyword.encode('ascii', 'ignore')), + CONTAINSSTRING(Keyword.q.keyword, self.toUTF8(keyword))) + return filterSimilarKeywords(keyword, + _iterKeywords(Keyword.select(constr)))[:results] + + def _get_keyword(self, keyword, results): + keyID = Keyword.select(Keyword.q.keyword == keyword) + if keyID.count() == 0: + return [] + keyID = keyID[0].id + movies = MovieKeyword.select(MovieKeyword.q.keywordID == + keyID)[:results] + return [(m.movieID, get_movie_data(m.movieID, self._kind)) + for m in movies] + + def _get_top_bottom_movies(self, kind): + if kind == 'top': + kind = 'top 250 rank' + elif kind == 'bottom': + # Not a refuse: the plain text data files contains only + # the bottom 10 movies. + kind = 'bottom 10 rank' + else: + return [] + infoID = InfoType.select(InfoType.q.info == kind) + if infoID.count() == 0: + return [] + infoID = infoID[0].id + movies = MovieInfoIdx.select(MovieInfoIdx.q.infoTypeID == infoID) + ml = [] + for m in movies: + minfo = get_movie_data(m.movieID, self._kind) + for k in kind, 'votes', 'rating', 'votes distribution': + valueDict = getSingleInfo(MovieInfoIdx, m.movieID, + k, notAList=True) + if k in (kind, 'votes') and k in valueDict: + valueDict[k] = int(valueDict[k]) + elif k == 'rating' and k in valueDict: + valueDict[k] = float(valueDict[k]) + minfo.update(valueDict) + ml.append((m.movieID, minfo)) + sorter = (_cmpBottom, _cmpTop)[kind == 'top 250 rank'] + ml.sort(sorter) + return ml + + def __del__(self): + """Ensure that the connection is closed.""" + if not hasattr(self, '_connection'): return + self._sql_logger.debug('closing connection to the database') + self._connection.close() + diff --git a/libs/imdb/parser/sql/alchemyadapter.py b/libs/imdb/parser/sql/alchemyadapter.py new file mode 100644 index 00000000..12cc494a --- /dev/null +++ b/libs/imdb/parser/sql/alchemyadapter.py @@ -0,0 +1,508 @@ +""" +parser.sql.alchemyadapter module (imdb.parser.sql package). + +This module adapts the SQLAlchemy ORM to the internal mechanism. + +Copyright 2008-2010 Davide Alberani + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +import re +import sys +import logging +from sqlalchemy import * +from sqlalchemy import schema +try: from sqlalchemy import exc # 0.5 +except ImportError: from sqlalchemy import exceptions as exc # 0.4 + +_alchemy_logger = logging.getLogger('imdbpy.parser.sql.alchemy') + +try: + import migrate.changeset + HAS_MC = True +except ImportError: + HAS_MC = False + _alchemy_logger.warn('Unable to import migrate.changeset: Foreign ' \ + 'Keys will not be created.') + +from imdb._exceptions import IMDbDataAccessError +from dbschema import * + +# Used to convert table and column names. +re_upper = re.compile(r'([A-Z])') + +# XXX: I'm not sure at all that this is the best method to connect +# to the database and bind that connection to every table. +metadata = MetaData() + +# Maps our placeholders to SQLAlchemy's column types. +MAP_COLS = { + INTCOL: Integer, + UNICODECOL: UnicodeText, + STRINGCOL: String +} + + +class NotFoundError(IMDbDataAccessError): + """Exception raised when Table.get(id) returns no value.""" + pass + + +def _renameTable(tname): + """Build the name of a table, as done by SQLObject.""" + tname = re_upper.sub(r'_\1', tname) + if tname.startswith('_'): + tname = tname[1:] + return tname.lower() + +def _renameColumn(cname): + """Build the name of a column, as done by SQLObject.""" + cname = cname.replace('ID', 'Id') + return _renameTable(cname) + + +class DNNameObj(object): + """Used to access table.sqlmeta.columns[column].dbName (a string).""" + def __init__(self, dbName): + self.dbName = dbName + + def __repr__(self): + return '' % (self.dbName, id(self)) + + +class DNNameDict(object): + """Used to access table.sqlmeta.columns (a dictionary).""" + def __init__(self, colMap): + self.colMap = colMap + + def __getitem__(self, key): + return DNNameObj(self.colMap[key]) + + def __repr__(self): + return '' % (self.colMap, id(self)) + + +class SQLMetaAdapter(object): + """Used to access table.sqlmeta (an object with .table, .columns and + .idName attributes).""" + def __init__(self, table, colMap=None): + self.table = table + if colMap is None: + colMap = {} + self.colMap = colMap + + def __getattr__(self, name): + if name == 'table': + return getattr(self.table, name) + if name == 'columns': + return DNNameDict(self.colMap) + if name == 'idName': + return self.colMap.get('id', 'id') + return None + + def __repr__(self): + return '' % \ + (repr(self.table), repr(self.colMap), id(self)) + + +class QAdapter(object): + """Used to access table.q attribute (remapped to SQLAlchemy table.c).""" + def __init__(self, table, colMap=None): + self.table = table + if colMap is None: + colMap = {} + self.colMap = colMap + + def __getattr__(self, name): + try: return getattr(self.table.c, self.colMap[name]) + except KeyError, e: raise AttributeError, "unable to get '%s'" % name + + def __repr__(self): + return '' % \ + (repr(self.table), repr(self.colMap), id(self)) + + +class RowAdapter(object): + """Adapter for a SQLAlchemy RowProxy object.""" + def __init__(self, row, table, colMap=None): + self.row = row + # FIXME: it's OBSCENE that 'table' should be passed from + # TableAdapter through ResultAdapter only to land here, + # where it's used to directly update a row item. + self.table = table + if colMap is None: + colMap = {} + self.colMap = colMap + self.colMapKeys = colMap.keys() + + def __getattr__(self, name): + try: return getattr(self.row, self.colMap[name]) + except KeyError, e: raise AttributeError, "unable to get '%s'" % name + + def __setattr__(self, name, value): + # FIXME: I can't even think about how much performances suffer, + # for this horrible hack (and it's used so rarely...) + # For sure something like a "property" to map column names + # to getter/setter functions would be much better, but it's + # not possible (or at least not easy) to build them for a + # single instance. + if name in self.__dict__.get('colMapKeys', ()): + # Trying to update a value in the database. + row = self.__dict__['row'] + table = self.__dict__['table'] + colMap = self.__dict__['colMap'] + params = {colMap[name]: value} + table.update(table.c.id==row.id).execute(**params) + # XXX: minor bug: after a value is assigned with the + # 'rowAdapterInstance.colName = value' syntax, for some + # reason rowAdapterInstance.colName still returns the + # previous value (even if the database is updated). + # Fix it? I'm not even sure it's ever used. + return + # For every other attribute. + object.__setattr__(self, name, value) + + def __repr__(self): + return '' % \ + (repr(self.row), repr(self.table), repr(self.colMap), id(self)) + + +class ResultAdapter(object): + """Adapter for a SQLAlchemy ResultProxy object.""" + def __init__(self, result, table, colMap=None): + self.result = result + self.table = table + if colMap is None: + colMap = {} + self.colMap = colMap + + def count(self): + return len(self) + + def __len__(self): + # FIXME: why sqlite returns -1? (that's wrooong!) + if self.result.rowcount == -1: + return 0 + return self.result.rowcount + + def __getitem__(self, key): + res = list(self.result)[key] + if not isinstance(key, slice): + # A single item. + return RowAdapter(res, self.table, colMap=self.colMap) + else: + # A (possible empty) list of items. + return [RowAdapter(x, self.table, colMap=self.colMap) + for x in res] + + def __iter__(self): + for item in self.result: + yield RowAdapter(item, self.table, colMap=self.colMap) + + def __repr__(self): + return '' % \ + (repr(self.result), repr(self.table), + repr(self.colMap), id(self)) + + +class TableAdapter(object): + """Adapter for a SQLAlchemy Table object, to mimic a SQLObject class.""" + def __init__(self, table, uri=None): + """Initialize a TableAdapter object.""" + self._imdbpySchema = table + self._imdbpyName = table.name + self.connectionURI = uri + self.colMap = {} + columns = [] + for col in table.cols: + # Column's paramters. + params = {'nullable': True} + params.update(col.params) + if col.name == 'id': + params['primary_key'] = True + if 'notNone' in params: + params['nullable'] = not params['notNone'] + del params['notNone'] + cname = _renameColumn(col.name) + self.colMap[col.name] = cname + colClass = MAP_COLS[col.kind] + colKindParams = {} + if 'length' in params: + colKindParams['length'] = params['length'] + del params['length'] + elif colClass is UnicodeText and col.index: + # XXX: limit length for UNICODECOLs that will have an index. + # this can result in name.name and title.title truncations! + colClass = Unicode + # Should work for most of the database servers. + length = 511 + if self.connectionURI: + if self.connectionURI.startswith('mysql'): + # To stay compatible with MySQL 4.x. + length = 255 + colKindParams['length'] = length + elif self._imdbpyName == 'PersonInfo' and col.name == 'info': + if self.connectionURI: + if self.connectionURI.startswith('ibm'): + # There are some entries longer than 32KB. + colClass = CLOB + # I really do hope that this space isn't wasted + # for each other shorter entry... + colKindParams['length'] = 68*1024 + colKind = colClass(**colKindParams) + if 'alternateID' in params: + # There's no need to handle them here. + del params['alternateID'] + # Create a column. + colObj = Column(cname, colKind, **params) + columns.append(colObj) + self.tableName = _renameTable(table.name) + # Create the table. + self.table = Table(self.tableName, metadata, *columns) + self._ta_insert = self.table.insert() + self._ta_select = self.table.select + # Adapters for special attributes. + self.q = QAdapter(self.table, colMap=self.colMap) + self.sqlmeta = SQLMetaAdapter(self.table, colMap=self.colMap) + + def select(self, conditions=None): + """Return a list of results.""" + result = self._ta_select(conditions).execute() + return ResultAdapter(result, self.table, colMap=self.colMap) + + def get(self, theID): + """Get an object given its ID.""" + result = self.select(self.table.c.id == theID) + #if not result: + # raise NotFoundError, 'no data for ID %s' % theID + # FIXME: isn't this a bit risky? We can't check len(result), + # because sqlite returns -1... + # What about converting it to a list and getting the first item? + try: + return result[0] + except KeyError: + raise NotFoundError, 'no data for ID %s' % theID + + def dropTable(self, checkfirst=True): + """Drop the table.""" + dropParams = {'checkfirst': checkfirst} + # Guess what? Another work-around for a ibm_db bug. + if self.table.bind.engine.url.drivername.startswith('ibm_db'): + del dropParams['checkfirst'] + try: + self.table.drop(**dropParams) + except exc.ProgrammingError: + # As above: re-raise the exception, but only if it's not ibm_db. + if not self.table.bind.engine.url.drivername.startswith('ibm_db'): + raise + + def createTable(self, checkfirst=True): + """Create the table.""" + self.table.create(checkfirst=checkfirst) + # Create indexes for alternateID columns (other indexes will be + # created later, at explicit request for performances reasons). + for col in self._imdbpySchema.cols: + if col.name == 'id': + continue + if col.params.get('alternateID', False): + self._createIndex(col, checkfirst=checkfirst) + + def _createIndex(self, col, checkfirst=True): + """Create an index for a given (schema) column.""" + # XXX: indexLen is ignored in SQLAlchemy, and that means that + # indexes will be over the whole 255 chars strings... + # NOTE: don't use a dot as a separator, or DB2 will do + # nasty things. + idx_name = '%s_%s' % (self.table.name, col.index or col.name) + if checkfirst: + for index in self.table.indexes: + if index.name == idx_name: + return + idx = Index(idx_name, getattr(self.table.c, self.colMap[col.name])) + # XXX: beware that exc.OperationalError can be raised, is some + # strange circumstances; that's why the index name doesn't + # follow the SQLObject convention, but includes the table name: + # sqlite, for example, expects index names to be unique at + # db-level. + try: + idx.create() + except exc.OperationalError, e: + _alchemy_logger.warn('Skipping creation of the %s.%s index: %s' % + (self.sqlmeta.table, col.name, e)) + + def addIndexes(self, ifNotExists=True): + """Create all required indexes.""" + for col in self._imdbpySchema.cols: + if col.index: + self._createIndex(col, checkfirst=ifNotExists) + + def addForeignKeys(self, mapTables, ifNotExists=True): + """Create all required foreign keys.""" + if not HAS_MC: + return + # It seems that there's no reason to prevent the creation of + # indexes for columns with FK constrains: if there's already + # an index, the FK index is not created. + countCols = 0 + for col in self._imdbpySchema.cols: + countCols += 1 + if not col.foreignKey: + continue + fks = col.foreignKey.split('.', 1) + foreignTableName = fks[0] + if len(fks) == 2: + foreignColName = fks[1] + else: + foreignColName = 'id' + foreignColName = mapTables[foreignTableName].colMap.get( + foreignColName, foreignColName) + thisColName = self.colMap.get(col.name, col.name) + thisCol = self.table.columns[thisColName] + foreignTable = mapTables[foreignTableName].table + foreignCol = getattr(foreignTable.c, foreignColName) + # Need to explicitly set an unique name, otherwise it will + # explode, if two cols points to the same table. + fkName = 'fk_%s_%s_%d' % (foreignTable.name, foreignColName, + countCols) + constrain = migrate.changeset.ForeignKeyConstraint([thisCol], + [foreignCol], + name=fkName) + try: + constrain.create() + except exc.OperationalError: + continue + + def __call__(self, *args, **kwds): + """To insert a new row with the syntax: TableClass(key=value, ...)""" + taArgs = {} + for key, value in kwds.items(): + taArgs[self.colMap.get(key, key)] = value + self._ta_insert.execute(*args, **taArgs) + + def __repr__(self): + return '' % (repr(self.table), id(self)) + + +# Module-level "cache" for SQLObject classes, to prevent +# "Table 'tableName' is already defined for this MetaData instance" errors, +# when two or more connections to the database are made. +# XXX: is this the best way to act? +TABLES_REPOSITORY = {} + +def getDBTables(uri=None): + """Return a list of TableAdapter objects to be used to access the + database through the SQLAlchemy ORM. The connection uri is optional, and + can be used to tailor the db schema to specific needs.""" + DB_TABLES = [] + for table in DB_SCHEMA: + if table.name in TABLES_REPOSITORY: + DB_TABLES.append(TABLES_REPOSITORY[table.name]) + continue + tableAdapter = TableAdapter(table, uri) + DB_TABLES.append(tableAdapter) + TABLES_REPOSITORY[table.name] = tableAdapter + return DB_TABLES + + +# Functions used to emulate SQLObject's logical operators. +def AND(*params): + """Emulate SQLObject's AND.""" + return and_(*params) + +def OR(*params): + """Emulate SQLObject's OR.""" + return or_(*params) + +def IN(item, inList): + """Emulate SQLObject's IN.""" + if not isinstance(item, schema.Column): + return OR(*[x == item for x in inList]) + else: + return item.in_(inList) + +def ISNULL(x): + """Emulate SQLObject's ISNULL.""" + # XXX: Should we use null()? Can null() be a global instance? + # XXX: Is it safe to test None with the == operator, in this case? + return x == None + +def ISNOTNULL(x): + """Emulate SQLObject's ISNOTNULL.""" + return x != None + +def CONTAINSSTRING(expr, pattern): + """Emulate SQLObject's CONTAINSSTRING.""" + return expr.like('%%%s%%' % pattern) + + +def toUTF8(s): + """For some strange reason, sometimes SQLObject wants utf8 strings + instead of unicode; with SQLAlchemy we just return the unicode text.""" + return s + + +class _AlchemyConnection(object): + """A proxy for the connection object, required since _ConnectionFairy + uses __slots__.""" + def __init__(self, conn): + self.conn = conn + + def __getattr__(self, name): + return getattr(self.conn, name) + + +def setConnection(uri, tables, encoding='utf8', debug=False): + """Set connection for every table.""" + # FIXME: why on earth MySQL requires an additional parameter, + # is well beyond my understanding... + if uri.startswith('mysql'): + if '?' in uri: + uri += '&' + else: + uri += '?' + uri += 'charset=%s' % encoding + params = {'encoding': encoding} + if debug: + params['echo'] = True + if uri.startswith('ibm_db'): + # Try to work-around a possible bug of the ibm_db DB2 driver. + params['convert_unicode'] = True + # XXX: is this the best way to connect? + engine = create_engine(uri, **params) + metadata.bind = engine + eng_conn = engine.connect() + if uri.startswith('sqlite'): + major = sys.version_info[0] + minor = sys.version_info[1] + if major > 2 or (major == 2 and minor > 5): + eng_conn.connection.connection.text_factory = str + # XXX: OH MY, THAT'S A MESS! + # We need to return a "connection" object, with the .dbName + # attribute set to the db engine name (e.g. "mysql"), .paramstyle + # set to the style of the paramters for query() calls, and the + # .module attribute set to a module (?) with .OperationalError and + # .IntegrityError attributes. + # Another attribute of "connection" is the getConnection() function, + # used to return an object with a .cursor() method. + connection = _AlchemyConnection(eng_conn.connection) + paramstyle = eng_conn.dialect.paramstyle + connection.module = eng_conn.dialect.dbapi + connection.paramstyle = paramstyle + connection.getConnection = lambda: connection.connection + connection.dbName = engine.url.drivername + return connection + + diff --git a/libs/imdb/parser/sql/cutils.c b/libs/imdb/parser/sql/cutils.c new file mode 100644 index 00000000..677c1b1e --- /dev/null +++ b/libs/imdb/parser/sql/cutils.c @@ -0,0 +1,269 @@ +/* + * cutils.c module. + * + * Miscellaneous functions to speed up the IMDbPY package. + * + * Contents: + * - pyratcliff(): + * Function that implements the Ratcliff-Obershelp comparison + * amongst Python strings. + * + * - pysoundex(): + * Return a soundex code string, for the given string. + * + * Copyright 2004-2009 Davide Alberani + * Released under the GPL license. + * + * NOTE: The Ratcliff-Obershelp part was heavily based on code from the + * "simil" Python module. + * The "simil" module is copyright of Luca Montecchiani + * and can be found here: http://spazioinwind.libero.it/montecchiani/ + * It was released under the GPL license; original comments are leaved + * below. + * + */ + + +/*========== Ratcliff-Obershelp ==========*/ +/***************************************************************************** + * + * Stolen code from : + * + * [Python-Dev] Why is soundex marked obsolete? + * by Eric S. Raymond [4]esr@thyrsus.com + * on Sun, 14 Jan 2001 14:09:01 -0500 + * + *****************************************************************************/ + +/***************************************************************************** + * + * Ratcliff-Obershelp common-subpattern similarity. + * + * This code first appeared in a letter to the editor in Doctor + * Dobbs's Journal, 11/1988. The original article on the algorithm, + * "Pattern Matching by Gestalt" by John Ratcliff, had appeared in the + * July 1988 issue (#181) but the algorithm was presented in assembly. + * The main drawback of the Ratcliff-Obershelp algorithm is the cost + * of the pairwise comparisons. It is significantly more expensive + * than stemming, Hamming distance, soundex, and the like. + * + * Running time quadratic in the data size, memory usage constant. + * + *****************************************************************************/ + +#include + +#define DONTCOMPARE_NULL 0.0 +#define DONTCOMPARE_SAME 1.0 +#define COMPARE 2.0 +#define STRING_MAXLENDIFFER 0.7 + +/* As of 05 Mar 2008, the longest title is ~600 chars. */ +#define MXLINELEN 1023 + +#define MAX(a,b) ((a) > (b) ? (a) : (b)) + + +//***************************************** +// preliminary check.... +//***************************************** +static float +strings_check(char const *s, char const *t) +{ + float threshold; // lenght difference + int s_len = strlen(s); // length of s + int t_len = strlen(t); // length of t + + // NULL strings ? + if ((t_len * s_len) == 0) + return (DONTCOMPARE_NULL); + + // the same ? + if (strcmp(s, t) == 0) + return (DONTCOMPARE_SAME); + + // string lenght difference threshold + // we don't want to compare too different lenght strings ;) + if (s_len < t_len) + threshold = (float) s_len / (float) t_len; + else + threshold = (float) t_len / (float) s_len; + if (threshold < STRING_MAXLENDIFFER) + return (DONTCOMPARE_NULL); + + // proceed + return (COMPARE); +} + + +static int +RatcliffObershelp(char *st1, char *end1, char *st2, char *end2) +{ + register char *a1, *a2; + char *b1, *b2; + char *s1 = st1, *s2 = st2; /* initializations are just to pacify GCC */ + short max, i; + + if (end1 <= st1 || end2 <= st2) + return (0); + if (end1 == st1 + 1 && end2 == st2 + 1) + return (0); + + max = 0; + b1 = end1; + b2 = end2; + + for (a1 = st1; a1 < b1; a1++) { + for (a2 = st2; a2 < b2; a2++) { + if (*a1 == *a2) { + /* determine length of common substring */ + for (i = 1; a1[i] && (a1[i] == a2[i]); i++) + continue; + if (i > max) { + max = i; + s1 = a1; + s2 = a2; + b1 = end1 - max; + b2 = end2 - max; + } + } + } + } + if (!max) + return (0); + max += RatcliffObershelp(s1 + max, end1, s2 + max, end2); /* rhs */ + max += RatcliffObershelp(st1, s1, st2, s2); /* lhs */ + return max; +} + + +static float +ratcliff(char *s1, char *s2) +/* compute Ratcliff-Obershelp similarity of two strings */ +{ + int l1, l2; + float res; + + // preliminary tests + res = strings_check(s1, s2); + if (res != COMPARE) + return(res); + + l1 = strlen(s1); + l2 = strlen(s2); + + return 2.0 * RatcliffObershelp(s1, s1 + l1, s2, s2 + l2) / (l1 + l2); +} + + +/* Change a string to lowercase. */ +static void +strtolower(char *s1) +{ + int i; + for (i=0; i < strlen(s1); i++) s1[i] = tolower(s1[i]); +} + + +/* Ratcliff-Obershelp for two python strings; returns a python float. */ +static PyObject* +pyratcliff(PyObject *self, PyObject *pArgs) +{ + char *s1 = NULL; + char *s2 = NULL; + PyObject *discard = NULL; + char s1copy[MXLINELEN+1]; + char s2copy[MXLINELEN+1]; + + /* The optional PyObject parameter is here to be compatible + * with the pure python implementation, which uses a + * difflib.SequenceMatcher object. */ + if (!PyArg_ParseTuple(pArgs, "ss|O", &s1, &s2, &discard)) + return NULL; + + strncpy(s1copy, s1, MXLINELEN); + strncpy(s2copy, s2, MXLINELEN); + /* Work on copies. */ + strtolower(s1copy); + strtolower(s2copy); + + return Py_BuildValue("f", ratcliff(s1copy, s2copy)); +} + + +/*========== soundex ==========*/ +/* Max length of the soundex code to output (an uppercase char and + * _at most_ 4 digits). */ +#define SOUNDEX_LEN 5 + +/* Group Number Lookup Table */ +static char soundTable[26] = +{ 0 /* A */, '1' /* B */, '2' /* C */, '3' /* D */, 0 /* E */, '1' /* F */, + '2' /* G */, 0 /* H */, 0 /* I */, '2' /* J */, '2' /* K */, '4' /* L */, + '5' /* M */, '5' /* N */, 0 /* O */, '1' /* P */, '2' /* Q */, '6' /* R */, + '2' /* S */, '3' /* T */, 0 /* U */, '1' /* V */, 0 /* W */, '2' /* X */, + 0 /* Y */, '2' /* Z */}; + +static PyObject* +pysoundex(PyObject *self, PyObject *pArgs) +{ + int i, j, n; + char *s = NULL; + char word[MXLINELEN+1]; + char soundCode[SOUNDEX_LEN+1]; + char c; + + if (!PyArg_ParseTuple(pArgs, "s", &s)) + return NULL; + + j = 0; + n = strlen(s); + + /* Convert to uppercase and exclude non-ascii chars. */ + for (i = 0; i < n; i++) { + c = toupper(s[i]); + if (c < 91 && c > 64) { + word[j] = c; + j++; + } + } + word[j] = '\0'; + + n = strlen(word); + if (n == 0) { + /* If the string is empty, returns None. */ + return Py_BuildValue(""); + } + soundCode[0] = word[0]; + + /* Build the soundCode string. */ + j = 1; + for (i = 1; j < SOUNDEX_LEN && i < n; i++) { + c = soundTable[(word[i]-65)]; + /* Compact zeroes and equal consecutive digits ("12234112"->"123412") */ + if (c != 0 && c != soundCode[j-1]) { + soundCode[j++] = c; + } + } + soundCode[j] = '\0'; + + return Py_BuildValue("s", soundCode); +} + + +static PyMethodDef cutils_methods[] = { + {"ratcliff", pyratcliff, + METH_VARARGS, "Ratcliff-Obershelp similarity."}, + {"soundex", pysoundex, + METH_VARARGS, "Soundex code for strings."}, + {NULL} +}; + + +void +initcutils(void) +{ + Py_InitModule("cutils", cutils_methods); +} + + diff --git a/libs/imdb/parser/sql/dbschema.py b/libs/imdb/parser/sql/dbschema.py new file mode 100644 index 00000000..358dfdd5 --- /dev/null +++ b/libs/imdb/parser/sql/dbschema.py @@ -0,0 +1,461 @@ +#-*- encoding: utf-8 -*- +""" +parser.sql.dbschema module (imdb.parser.sql package). + +This module provides the schema used to describe the layout of the +database used by the imdb.parser.sql package; functions to create/drop +tables and indexes are also provided. + +Copyright 2005-2010 Davide Alberani + 2006 Giuseppe "Cowo" Corbelli lugbs.linux.it> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +import logging + +_dbschema_logger = logging.getLogger('imdbpy.parser.sql.dbschema') + + +# Placeholders for column types. +INTCOL = 1 +UNICODECOL = 2 +STRINGCOL = 3 +_strMap = {1: 'INTCOL', 2: 'UNICODECOL', 3: 'STRINGCOL'} + +class DBCol(object): + """Define column objects.""" + def __init__(self, name, kind, **params): + self.name = name + self.kind = kind + self.index = None + self.indexLen = None + # If not None, two notations are accepted: 'TableName' + # and 'TableName.ColName'; in the first case, 'id' is assumed + # as the name of the pointed column. + self.foreignKey = None + if 'index' in params: + self.index = params['index'] + del params['index'] + if 'indexLen' in params: + self.indexLen = params['indexLen'] + del params['indexLen'] + if 'foreignKey' in params: + self.foreignKey = params['foreignKey'] + del params['foreignKey'] + self.params = params + + def __str__(self): + """Class representation.""" + s = '' % (self.name, + len(self.cols), sum([len(v) for v in self.values.values()])) + + def __repr__(self): + """Class representation.""" + s = '').lstrip('<') + for col in self.cols]) + if col_s: + s += ', %s' % col_s + if self.values: + s += ', values=%s' % self.values + s += ')>' + return s + + +# Default values to insert in some tables: {'column': (list, of, values, ...)} +kindTypeDefs = {'kind': ('movie', 'tv series', 'tv movie', 'video movie', + 'tv mini series', 'video game', 'episode')} +companyTypeDefs = {'kind': ('distributors', 'production companies', + 'special effects companies', 'miscellaneous companies')} +infoTypeDefs = {'info': ('runtimes', 'color info', 'genres', 'languages', + 'certificates', 'sound mix', 'tech info', 'countries', 'taglines', + 'keywords', 'alternate versions', 'crazy credits', 'goofs', + 'soundtrack', 'quotes', 'release dates', 'trivia', 'locations', + 'mini biography', 'birth notes', 'birth date', 'height', + 'death date', 'spouse', 'other works', 'birth name', + 'salary history', 'nick names', 'books', 'agent address', + 'biographical movies', 'portrayed in', 'where now', 'trade mark', + 'interviews', 'article', 'magazine cover photo', 'pictorial', + 'death notes', 'LD disc format', 'LD year', 'LD digital sound', + 'LD official retail price', 'LD frequency response', 'LD pressing plant', + 'LD length', 'LD language', 'LD review', 'LD spaciality', 'LD release date', + 'LD production country', 'LD contrast', 'LD color rendition', + 'LD picture format', 'LD video noise', 'LD video artifacts', + 'LD release country', 'LD sharpness', 'LD dynamic range', + 'LD audio noise', 'LD color information', 'LD group genre', + 'LD quality program', 'LD close captions-teletext-ld-g', + 'LD category', 'LD analog left', 'LD certification', + 'LD audio quality', 'LD video quality', 'LD aspect ratio', + 'LD analog right', 'LD additional information', + 'LD number of chapter stops', 'LD dialogue intellegibility', + 'LD disc size', 'LD master format', 'LD subtitles', + 'LD status of availablility', 'LD quality of source', + 'LD number of sides', 'LD video standard', 'LD supplement', + 'LD original title', 'LD sound encoding', 'LD number', 'LD label', + 'LD catalog number', 'LD laserdisc title', 'screenplay-teleplay', + 'novel', 'adaption', 'book', 'production process protocol', + 'printed media reviews', 'essays', 'other literature', 'mpaa', + 'plot', 'votes distribution', 'votes', 'rating', + 'production dates', 'copyright holder', 'filming dates', 'budget', + 'weekend gross', 'gross', 'opening weekend', 'rentals', + 'admissions', 'studios', 'top 250 rank', 'bottom 10 rank')} +compCastTypeDefs = {'kind': ('cast', 'crew', 'complete', 'complete+verified')} +linkTypeDefs = {'link': ('follows', 'followed by', 'remake of', 'remade as', + 'references', 'referenced in', 'spoofs', 'spoofed in', + 'features', 'featured in', 'spin off from', 'spin off', + 'version of', 'similar to', 'edited into', + 'edited from', 'alternate language version of', + 'unknown link')} +roleTypeDefs = {'role': ('actor', 'actress', 'producer', 'writer', + 'cinematographer', 'composer', 'costume designer', + 'director', 'editor', 'miscellaneous crew', + 'production designer', 'guest')} + +# Schema of tables in our database. +# XXX: Foreign keys can be used to create constrains between tables, +# but they create indexes in the database, and this +# means poor performances at insert-time. +DB_SCHEMA = [ + DBTable('Name', + # namePcodeCf is the soundex of the name in the canonical format. + # namePcodeNf is the soundex of the name in the normal format, if + # different from namePcodeCf. + # surnamePcode is the soundex of the surname, if different from the + # other two values. + + # The 'id' column is simply skipped by SQLObject (it's a default); + # the alternateID attribute here will be ignored by SQLAlchemy. + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('name', UNICODECOL, notNone=True, index='idx_name', indexLen=6), + DBCol('imdbIndex', UNICODECOL, length=12, default=None), + DBCol('imdbID', INTCOL, default=None), + DBCol('namePcodeCf', STRINGCOL, length=5, default=None, + index='idx_pcodecf'), + DBCol('namePcodeNf', STRINGCOL, length=5, default=None, + index='idx_pcodenf'), + DBCol('surnamePcode', STRINGCOL, length=5, default=None, + index='idx_pcode'), + DBCol('md5sum', STRINGCOL, length=32, default=None, index='idx_md5') + ), + + DBTable('CharName', + # namePcodeNf is the soundex of the name in the normal format. + # surnamePcode is the soundex of the surname, if different + # from namePcodeNf. + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('name', UNICODECOL, notNone=True, index='idx_name', indexLen=6), + DBCol('imdbIndex', UNICODECOL, length=12, default=None), + DBCol('imdbID', INTCOL, default=None), + DBCol('namePcodeNf', STRINGCOL, length=5, default=None, + index='idx_pcodenf'), + DBCol('surnamePcode', STRINGCOL, length=5, default=None, + index='idx_pcode'), + DBCol('md5sum', STRINGCOL, length=32, default=None, index='idx_md5') + ), + + DBTable('CompanyName', + # namePcodeNf is the soundex of the name in the normal format. + # namePcodeSf is the soundex of the name plus the country code. + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('name', UNICODECOL, notNone=True, index='idx_name', indexLen=6), + DBCol('countryCode', UNICODECOL, length=255, default=None), + DBCol('imdbID', INTCOL, default=None), + DBCol('namePcodeNf', STRINGCOL, length=5, default=None, + index='idx_pcodenf'), + DBCol('namePcodeSf', STRINGCOL, length=5, default=None, + index='idx_pcodesf'), + DBCol('md5sum', STRINGCOL, length=32, default=None, index='idx_md5') + ), + + DBTable('KindType', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('kind', STRINGCOL, length=15, default=None, alternateID=True), + values=kindTypeDefs + ), + + DBTable('Title', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('title', UNICODECOL, notNone=True, + index='idx_title', indexLen=10), + DBCol('imdbIndex', UNICODECOL, length=12, default=None), + DBCol('kindID', INTCOL, notNone=True, foreignKey='KindType'), + DBCol('productionYear', INTCOL, default=None), + DBCol('imdbID', INTCOL, default=None), + DBCol('phoneticCode', STRINGCOL, length=5, default=None, + index='idx_pcode'), + DBCol('episodeOfID', INTCOL, default=None, index='idx_epof', + foreignKey='Title'), + DBCol('seasonNr', INTCOL, default=None), + DBCol('episodeNr', INTCOL, default=None), + # Maximum observed length is 44; 49 can store 5 comma-separated + # year-year pairs. + DBCol('seriesYears', STRINGCOL, length=49, default=None), + DBCol('md5sum', STRINGCOL, length=32, default=None, index='idx_md5') + ), + + DBTable('CompanyType', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('kind', STRINGCOL, length=32, default=None, alternateID=True), + values=companyTypeDefs + ), + + DBTable('AkaName', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('personID', INTCOL, notNone=True, index='idx_person', + foreignKey='Name'), + DBCol('name', UNICODECOL, notNone=True), + DBCol('imdbIndex', UNICODECOL, length=12, default=None), + DBCol('namePcodeCf', STRINGCOL, length=5, default=None, + index='idx_pcodecf'), + DBCol('namePcodeNf', STRINGCOL, length=5, default=None, + index='idx_pcodenf'), + DBCol('surnamePcode', STRINGCOL, length=5, default=None, + index='idx_pcode'), + DBCol('md5sum', STRINGCOL, length=32, default=None, index='idx_md5') + ), + + DBTable('AkaTitle', + # XXX: It's safer to set notNone to False, here. + # alias for akas are stored completely in the AkaTitle table; + # this means that episodes will set also a "tv series" alias name. + # Reading the aka-title.list file it looks like there are + # episode titles with aliases to different titles for both + # the episode and the series title, while for just the series + # there are no aliases. + # E.g.: + # aka title original title + # "Series, The" (2005) {The Episode} "Other Title" (2005) {Other Title} + # But there is no: + # "Series, The" (2005) "Other Title" (2005) + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('movieID', INTCOL, notNone=True, index='idx_movieid', + foreignKey='Title'), + DBCol('title', UNICODECOL, notNone=True), + DBCol('imdbIndex', UNICODECOL, length=12, default=None), + DBCol('kindID', INTCOL, notNone=True, foreignKey='KindType'), + DBCol('productionYear', INTCOL, default=None), + DBCol('phoneticCode', STRINGCOL, length=5, default=None, + index='idx_pcode'), + DBCol('episodeOfID', INTCOL, default=None, index='idx_epof', + foreignKey='AkaTitle'), + DBCol('seasonNr', INTCOL, default=None), + DBCol('episodeNr', INTCOL, default=None), + DBCol('note', UNICODECOL, default=None), + DBCol('md5sum', STRINGCOL, length=32, default=None, index='idx_md5') + ), + + DBTable('RoleType', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('role', STRINGCOL, length=32, notNone=True, alternateID=True), + values=roleTypeDefs + ), + + DBTable('CastInfo', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('personID', INTCOL, notNone=True, index='idx_pid', + foreignKey='Name'), + DBCol('movieID', INTCOL, notNone=True, index='idx_mid', + foreignKey='Title'), + DBCol('personRoleID', INTCOL, default=None, index='idx_cid', + foreignKey='CharName'), + DBCol('note', UNICODECOL, default=None), + DBCol('nrOrder', INTCOL, default=None), + DBCol('roleID', INTCOL, notNone=True, foreignKey='RoleType') + ), + + DBTable('CompCastType', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('kind', STRINGCOL, length=32, notNone=True, alternateID=True), + values=compCastTypeDefs + ), + + DBTable('CompleteCast', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('movieID', INTCOL, index='idx_mid', foreignKey='Title'), + DBCol('subjectID', INTCOL, notNone=True, foreignKey='CompCastType'), + DBCol('statusID', INTCOL, notNone=True, foreignKey='CompCastType') + ), + + DBTable('InfoType', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('info', STRINGCOL, length=32, notNone=True, alternateID=True), + values=infoTypeDefs + ), + + DBTable('LinkType', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('link', STRINGCOL, length=32, notNone=True, alternateID=True), + values=linkTypeDefs + ), + + DBTable('Keyword', + DBCol('id', INTCOL, notNone=True, alternateID=True), + # XXX: can't use alternateID=True, because it would create + # a UNIQUE index; unfortunately (at least with a common + # collation like utf8_unicode_ci) MySQL will consider + # some different keywords identical - like + # "fiancée" and "fiancee". + DBCol('keyword', UNICODECOL, length=255, notNone=True, + index='idx_keyword', indexLen=5), + DBCol('phoneticCode', STRINGCOL, length=5, default=None, + index='idx_pcode') + ), + + DBTable('MovieKeyword', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('movieID', INTCOL, notNone=True, index='idx_mid', + foreignKey='Title'), + DBCol('keywordID', INTCOL, notNone=True, index='idx_keywordid', + foreignKey='Keyword') + ), + + DBTable('MovieLink', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('movieID', INTCOL, notNone=True, index='idx_mid', + foreignKey='Title'), + DBCol('linkedMovieID', INTCOL, notNone=True, foreignKey='Title'), + DBCol('linkTypeID', INTCOL, notNone=True, foreignKey='LinkType') + ), + + DBTable('MovieInfo', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('movieID', INTCOL, notNone=True, index='idx_mid', + foreignKey='Title'), + DBCol('infoTypeID', INTCOL, notNone=True, foreignKey='InfoType'), + DBCol('info', UNICODECOL, notNone=True), + DBCol('note', UNICODECOL, default=None) + ), + + # This table is identical to MovieInfo, except that both 'infoTypeID' + # and 'info' are indexed. + DBTable('MovieInfoIdx', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('movieID', INTCOL, notNone=True, index='idx_mid', + foreignKey='Title'), + DBCol('infoTypeID', INTCOL, notNone=True, index='idx_infotypeid', + foreignKey='InfoType'), + DBCol('info', UNICODECOL, notNone=True, index='idx_info', indexLen=10), + DBCol('note', UNICODECOL, default=None) + ), + + DBTable('MovieCompanies', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('movieID', INTCOL, notNone=True, index='idx_mid', + foreignKey='Title'), + DBCol('companyID', INTCOL, notNone=True, index='idx_cid', + foreignKey='CompanyName'), + DBCol('companyTypeID', INTCOL, notNone=True, foreignKey='CompanyType'), + DBCol('note', UNICODECOL, default=None) + ), + + DBTable('PersonInfo', + DBCol('id', INTCOL, notNone=True, alternateID=True), + DBCol('personID', INTCOL, notNone=True, index='idx_pid', + foreignKey='Name'), + DBCol('infoTypeID', INTCOL, notNone=True, foreignKey='InfoType'), + DBCol('info', UNICODECOL, notNone=True), + DBCol('note', UNICODECOL, default=None) + ) +] + + +# Functions to manage tables. +def dropTables(tables, ifExists=True): + """Drop the tables.""" + # In reverse order (useful to avoid errors about foreign keys). + DB_TABLES_DROP = list(tables) + DB_TABLES_DROP.reverse() + for table in DB_TABLES_DROP: + _dbschema_logger.info('dropping table %s', table._imdbpyName) + table.dropTable(ifExists) + +def createTables(tables, ifNotExists=True): + """Create the tables and insert default values.""" + for table in tables: + # Create the table. + _dbschema_logger.info('creating table %s', table._imdbpyName) + table.createTable(ifNotExists) + # Insert default values, if any. + if table._imdbpySchema.values: + _dbschema_logger.info('inserting values into table %s', + table._imdbpyName) + for key in table._imdbpySchema.values: + for value in table._imdbpySchema.values[key]: + table(**{key: unicode(value)}) + +def createIndexes(tables, ifNotExists=True): + """Create the indexes in the database.""" + for table in tables: + _dbschema_logger.info('creating indexes for table %s', + table._imdbpyName) + table.addIndexes(ifNotExists) + +def createForeignKeys(tables, ifNotExists=True): + """Create Foreign Keys.""" + mapTables = {} + for table in tables: + mapTables[table._imdbpyName] = table + for table in tables: + _dbschema_logger.info('creating foreign keys for table %s', + table._imdbpyName) + table.addForeignKeys(mapTables, ifNotExists) + diff --git a/libs/imdb/parser/sql/objectadapter.py b/libs/imdb/parser/sql/objectadapter.py new file mode 100644 index 00000000..b7ca9851 --- /dev/null +++ b/libs/imdb/parser/sql/objectadapter.py @@ -0,0 +1,203 @@ +""" +parser.sql.objectadapter module (imdb.parser.sql package). + +This module adapts the SQLObject ORM to the internal mechanism. + +Copyright 2008-2010 Davide Alberani + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +import sys +import logging + +from sqlobject import * +from sqlobject.sqlbuilder import ISNULL, ISNOTNULL, AND, OR, IN, CONTAINSSTRING + +from dbschema import * + +_object_logger = logging.getLogger('imdbpy.parser.sql.object') + + +# Maps our placeholders to SQLAlchemy's column types. +MAP_COLS = { + INTCOL: IntCol, + UNICODECOL: UnicodeCol, + STRINGCOL: StringCol +} + + +# Exception raised when Table.get(id) returns no value. +NotFoundError = SQLObjectNotFound + + +# class method to be added to the SQLObject class. +def addIndexes(cls, ifNotExists=True): + """Create all required indexes.""" + for col in cls._imdbpySchema.cols: + if col.index: + idxName = col.index + colToIdx = col.name + if col.indexLen: + colToIdx = {'column': col.name, 'length': col.indexLen} + if idxName in [i.name for i in cls.sqlmeta.indexes]: + # Check if the index is already present. + continue + idx = DatabaseIndex(colToIdx, name=idxName) + cls.sqlmeta.addIndex(idx) + try: + cls.createIndexes(ifNotExists) + except dberrors.OperationalError, e: + _object_logger.warn('Skipping creation of the %s.%s index: %s' % + (cls.sqlmeta.table, col.name, e)) +addIndexes = classmethod(addIndexes) + + +# Global repository for "fake" tables with Foreign Keys - need to +# prevent troubles if addForeignKeys is called more than one time. +FAKE_TABLES_REPOSITORY = {} + +def _buildFakeFKTable(cls, fakeTableName): + """Return a "fake" table, with foreign keys where needed.""" + countCols = 0 + attrs = {} + for col in cls._imdbpySchema.cols: + countCols += 1 + if col.name == 'id': + continue + if not col.foreignKey: + # A non-foreign key column - add it as usual. + attrs[col.name] = MAP_COLS[col.kind](**col.params) + continue + # XXX: Foreign Keys pointing to TableName.ColName not yet supported. + thisColName = col.name + if thisColName.endswith('ID'): + thisColName = thisColName[:-2] + + fks = col.foreignKey.split('.', 1) + foreignTableName = fks[0] + if len(fks) == 2: + foreignColName = fks[1] + else: + foreignColName = 'id' + # Unused... + #fkName = 'fk_%s_%s_%d' % (foreignTableName, foreignColName, + # countCols) + # Create a Foreign Key column, with the correct references. + fk = ForeignKey(foreignTableName, name=thisColName, default=None) + attrs[thisColName] = fk + # Build a _NEW_ SQLObject subclass, with foreign keys, if needed. + newcls = type(fakeTableName, (SQLObject,), attrs) + return newcls + +def addForeignKeys(cls, mapTables, ifNotExists=True): + """Create all required foreign keys.""" + # Do not even try, if there are no FK, in this table. + if not filter(None, [col.foreignKey for col in cls._imdbpySchema.cols]): + return + fakeTableName = 'myfaketable%s' % cls.sqlmeta.table + if fakeTableName in FAKE_TABLES_REPOSITORY: + newcls = FAKE_TABLES_REPOSITORY[fakeTableName] + else: + newcls = _buildFakeFKTable(cls, fakeTableName) + FAKE_TABLES_REPOSITORY[fakeTableName] = newcls + # Connect the class with foreign keys. + newcls.setConnection(cls._connection) + for col in cls._imdbpySchema.cols: + if col.name == 'id': + continue + if not col.foreignKey: + continue + # Get the SQL that _WOULD BE_ run, if we had to create + # this "fake" table. + fkQuery = newcls._connection.createReferenceConstraint(newcls, + newcls.sqlmeta.columns[col.name]) + if not fkQuery: + # Probably the db doesn't support foreign keys (SQLite). + continue + # Remove "myfaketable" to get references to _real_ tables. + fkQuery = fkQuery.replace('myfaketable', '') + # Execute the query. + newcls._connection.query(fkQuery) + # Disconnect it. + newcls._connection.close() +addForeignKeys = classmethod(addForeignKeys) + + +# Module-level "cache" for SQLObject classes, to prevent +# "class TheClass is already in the registry" errors, when +# two or more connections to the database are made. +# XXX: is this the best way to act? +TABLES_REPOSITORY = {} + +def getDBTables(uri=None): + """Return a list of classes to be used to access the database + through the SQLObject ORM. The connection uri is optional, and + can be used to tailor the db schema to specific needs.""" + DB_TABLES = [] + for table in DB_SCHEMA: + if table.name in TABLES_REPOSITORY: + DB_TABLES.append(TABLES_REPOSITORY[table.name]) + continue + attrs = {'_imdbpyName': table.name, '_imdbpySchema': table, + 'addIndexes': addIndexes, 'addForeignKeys': addForeignKeys} + for col in table.cols: + if col.name == 'id': + continue + attrs[col.name] = MAP_COLS[col.kind](**col.params) + # Create a subclass of SQLObject. + # XXX: use a metaclass? I can't see any advantage. + cls = type(table.name, (SQLObject,), attrs) + DB_TABLES.append(cls) + TABLES_REPOSITORY[table.name] = cls + return DB_TABLES + + +def toUTF8(s): + """For some strange reason, sometimes SQLObject wants utf8 strings + instead of unicode.""" + return s.encode('utf_8') + + +def setConnection(uri, tables, encoding='utf8', debug=False): + """Set connection for every table.""" + kw = {} + # FIXME: it's absolutely unclear what we should do to correctly + # support unicode in MySQL; with some versions of SQLObject, + # it seems that setting use_unicode=1 is the _wrong_ thing to do. + _uriLower = uri.lower() + if _uriLower.startswith('mysql'): + kw['use_unicode'] = 1 + #kw['sqlobject_encoding'] = encoding + kw['charset'] = encoding + conn = connectionForURI(uri, **kw) + conn.debug = debug + if uri.startswith('sqlite'): + major = sys.version_info[0] + minor = sys.version_info[1] + if major > 2 or (major == 2 and minor > 5): + conn.connection.connection.text_factory = str + for table in tables: + table.setConnection(conn) + #table.sqlmeta.cacheValues = False + # FIXME: is it safe to set table._cacheValue to False? Looks like + # we can't retrieve correct values after an update (I think + # it's never needed, but...) Anyway, these are set to False + # for performance reason at insert time (see imdbpy2sql.py). + table._cacheValue = False + # Required by imdbpy2sql.py. + conn.paramstyle = conn.module.paramstyle + return conn + diff --git a/libs/imdb/utils.py b/libs/imdb/utils.py index 7d726978..e2d9551d 100644 --- a/libs/imdb/utils.py +++ b/libs/imdb/utils.py @@ -141,6 +141,9 @@ def analyze_name(name, canonical=None): if cpi > opi and re_index.match(name[opi:cpi+1]): imdbIndex = name[opi+1:cpi] name = name[:opi].rstrip() + else: + # XXX: for the birth and death dates case like " (1926-2004)" + name = name[:opi-1] if not name: raise IMDbParserError, 'invalid name: "%s"' % original_n if canonical is not None: @@ -377,6 +380,9 @@ def analyze_title(title, canonical=None, canonicalSeries=None, elif title.endswith('(V)'): kind = u'video movie' title = title[:-3].rstrip() + elif title.endswith('(video)'): + kind = u'video movie' + title = title[:-7].rstrip() elif title.endswith('(mini)'): kind = u'tv mini series' title = title[:-6].rstrip() @@ -400,6 +406,9 @@ def analyze_title(title, canonical=None, canonicalSeries=None, if not kind: kind = u'tv series' title = title[1:-1].strip() + elif title.endswith('(TV series)'): + kind = u'tv series' + title = title[:-11].rstrip() if not title: raise IMDbParserError, 'invalid title: "%s"' % original_t if canonical is not None: