guessit-0.11.0/0000775000175000017500000000000012572402126014051 5ustar toilaltoilal00000000000000guessit-0.11.0/HISTORY.rst0000664000175000017500000002404312572401752015753 0ustar toilaltoilal00000000000000History ======= 0.11.0 (2015-09-04) ------------------- * Fixed year-season episodes with 'x' separator * Fixed name guessing when a subdirectory contains a number * Fixed possible IndexError in release_group plugin * Fixed infinite recursion when multiple languages from same node are ignored in the second pass * Added skip of language guess for 2-3 letters directories * Added exclusion of common words from title guessing * Added a higher confidence on filename over directories 0.10.4 (2015-08-19) ------------------- * Added ``LD``/``MD`` properties * Added better support for ``episodeList`` * Added more rules for filetype autodetection * Added support for ``episodeList`` on weak episode patterns * Added ``partList`` property (list for ``part`` property) * Added vob to supported file extensions * Added more ignore words to language detection * Added string options support for API methods (will be parsed like command-line) * Added better subtitle detection (prefix priority over suffix) * Fixed ``version`` property no detected when detached from ``episodeNumber`` * Fixed ``releaseGroup`` property no detected when prefixed by ``screenSize`` * Fixed single digit detected as an ``episodeNumber`` * Fixed an internal issue in matcher causing absolute and relative group spans confusion * Fixed an internal issue in properties container causing invalid ordering of found patterns * Fixed raw value for some properties (--advanced) * Use pytest as test runner * Remove support for python 2.6 0.10.3 (2015-04-04) ------------------- * Fix issues related to unicode encoding/decoding * Fix possible crashes in guess_video_rexps * Fix invalid guess result when crc32 contains 6 digits than can be parsed as a date 0.10.2 (2015-03-08) ------------------- * Use common words to resolve conflicts on strings * Bump babelfish version * Fix setuptools deprecation warning * Package argparse dependency only if python<2.7 0.10.1 (2015-01-05) ------------------- * Avoid word Stay to be recognized as AY subtitle * Fixed exception when no unidentified leaves remains * Avoid usage of deprecated EntryPoint.load() require argument * Fixed invalid raw data for some properties (title, series and maybe others) 0.10.0 (2014-12-27) ------------------- * Fixed exception when serie title starts with Ep * Fixed exception when trying to parse a full length country name * Removed deprecated optparse module, replaced by argparse 0.9.4 (2014-11-10) ------------------ * Fixed exception when filename contains multiple languages ISO codes * Fixed transformers initialization logging * Fixed possible exception in language transformer * Added more words to common english words 0.9.3 (2014-09-14) ------------------ * Added ``Preair`` and ``Remux`` to ``other`` property * Better detection of ``audioProfile`` = ``HD`` / ``HDMA`` for ``audioCodec`` = ``DTS`` * Better detection of ``format``` = ``BluRay`` (when followed by Rip) * Recognize ``RC`` as ``R5`` * Recognize ``WEB-HD```and ``ẀEB`` as ``WEB-DL`` 0.9.2 (2014-09-13) ------------------ * Added support of option registration on transformers * Better detection of ``releaseGroup`` when using ``expected-series`` or ``expected-title`` option * Better ``audioChannel`` = ``5.1`` / ``7.1`` guessing (``6ch``, ``8ch``) * Fixed usage not showing when invalid options were passed * Added ``PAL``, ``SECAM`` and ``NTSC`` to ``other`` possible values * Recognize DVD-9 and DVD-5 as ``format`` = ``DVD`` property 0.9.1 (2014-09-06) ------------------ * Added ``--unidentified`` option to display unidentified parts of the filename This option affects command line only - From API `unidentified` properties will always be grabbed regardless this settings * Better guessing of ``releaseGroup`` property * Added ``mHD`` and ``HDLight`` to ``other properties`` * Better guessing of ``format`` = ``DVD`` property (DVD-R pattern) * Some ``info`` logs changed to ``debug`` for quiet integration * Small fixes 0.9.0 (2014-09-05) ------------------ * Better auto-detection of anime episodes, containing a ``crc32`` or a digits ``episodeNumber``. * Better listing of options on ``guessit -h`` * Added ``--allowed-countries`` and ``--allowed-languages`` to avoid two or three letters words to be guessed as ``country`` or ``language`` * Added ``--disabled-transformers`` option to disable transformer plugin at runtime. * Added ``--episode-prefer-number`` option, for ``guess -t episode 'serie.123.avi'`` to return ``episodeNumber`` = ``123`` instead of ``season`` = ``1`` + ``episodeNumber`` = 23`` * Added ``--split-camel`` option (now disabled by default) * Added ``episodeCount`` and ``seasonCount`` properties (x-of-n notation) * Added ``--date-year-first``` and ``--date-day-first`` options * Added ``--expected-title``, ``--expected-series`` and ``--expected-groups`` to help finding values when those properties are known * Added ``10bit`` value to ``videoProfile`` * Added ``--show-property`` option to only show a single property * Added ``--input-file`` option to parse a list of * Added ``--version`` option * Added ``ass```to subtitle extensions * Added ``Fansub`` value for ``other`` property * Added more date formats support with ``dateutil`` dependency * Added customizable ``clean_function`` (API) * Added ``default_options`` (API) * Fixed ``--yaml`` option to support ``language`` and ``country`` * Fixed ``transformers.add_transformer()`` function (API) 0.8 (2014-07-06) ---------------- * New webservice that allows to use GuessIt just by sending a POST request to the http://guessit.io/guess url * Command-line util can now report bugs to the http://guessit.io/bugs service by specifying the ``-b`` or ``--bug`` flag * GuessIt can now use the Enzyme python package to detect metadata out of the actual video file metadata instead of the filename * Finished transition to ``babelfish.Language`` and ``babelfish.Country`` * New property: ``duration`` which returns the duration of the video in seconds This requires the Enzyme package to work * New property: ``fileSize`` which returns the size of the file in bytes * Renamed property ``special`` to ``episodeDetails`` * Added support for Python 3.4 * Optimization and bugfixes 0.7.1 (2014-03-03) ------------------ * New property "special": values can be trailer, pilot, unaired * New options for the guessit cmdline util: ``-y``, ``--yaml`` outputs the result in yaml format and ``-n``, ``--name-only`` analyzes the input as simple text (instead of filename) * Added properties formatters and validators * Removed support for python 3.2 * A healthy amount of code cleanup/refactoring and fixes :) 0.7 (2014-01-29) ---------------- * New plugin API that allows to register custom patterns / transformers * Uses Babelfish for language and country detection * Added Quality API to rate file quality from guessed property values * Better and more accurate overall detection * Added roman and word numeral detection * Added 'videoProfile' and 'audioProfile' property * Moved boolean properties to 'other' property value ('is3D' became 'other' = '3D') * Added more possible values for various properties. * Added command line option to list available properties and values * Fixes for Python3 support 0.6.2 (2013-11-08) ------------------ * Added support for nfo files * GuessIt can now output advanced information as json ('-a' on the command line) * Better language detection * Added new property: 'is3D' 0.6.1 (2013-09-18) ------------------ * New property "idNumber" that tries to identify a hash value or a serial number * The usual bugfixes 0.6 (2013-07-16) ---------------- * Better packaging: unittests and doc included in source tarball * Fixes everywhere: unicode, release group detection, language detection, ... * A few speed optimizations 0.5.4 (2013-02-11) ------------------ * guessit can be installed as a system wide script (thanks @dplarson) * Enhanced logging facilities * Fixes for episode number and country detection 0.5.3 (2012-11-01) ------------------ * GuessIt can now optionally act as a wrapper around the 'guess-language' python module, and thus provide detection of the natural language in which a body of text is written * Lots of fixes everywhere, mostly for properties and release group detection 0.5.2 (2012-10-02) ------------------ * Much improved auto-detection of filetype * Fixed some issues with the detection of release groups 0.5.1 (2012-09-23) ------------------ * now detects 'country' property; also detect 'year' property for series * more patterns and bugfixes 0.5 (2012-07-29) ---------------- * Python3 compatibility * the usual assortment of bugfixes 0.4.2 (2012-05-19) ------------------ * added Language.tmdb language code property for TheMovieDB * added ability to recognize list of episodes * bugfixes for Language.__nonzero__ and episode regexps 0.4.1 (2012-05-12) ------------------ * bugfixes for unicode, paths on Windows, autodetection, and language issues 0.4 (2012-04-28) ---------------- * much improved language detection, now also detect language variants * supports more video filetypes (thanks to Rob McMullen) 0.3.1 (2012-03-15) ------------------ * fixed package installation from PyPI * better imports for the transformations (thanks Diaoul!) * some small language fixes 0.3 (2012-03-12) ---------------- * fix to recognize 1080p format (thanks to Jonathan Lauwers) 0.3b2 (2012-03-02) ------------------ * fixed the package installation 0.3b1 (2012-03-01) ------------------ * refactored quite a bit, code is much cleaner now * fixed quite a few tests * re-vamped the documentation, wrote some more 0.2 (2011-05-27) ---------------- * new parser/matcher completely replaced the old one * quite a few more unittests and fixes 0.2b1 (2011-05-20) ------------------ * brand new parser/matcher that is much more flexible and powerful * lots of cleaning and a bunch of unittests 0.1 (2011-05-10) ---------------- * fixed a few minor issues & heuristics 0.1b2 (2011-03-12) ------------------ * Added PyPI trove classifiers * fixed version number in setup.py 0.1b1 (2011-03-12) ------------------ * first pre-release version; imported from Smewt with a few enhancements already in there. guessit-0.11.0/guessit.egg-info/0000775000175000017500000000000012572402126017226 5ustar toilaltoilal00000000000000guessit-0.11.0/guessit.egg-info/entry_points.txt0000664000175000017500000000006312572402126022523 0ustar toilaltoilal00000000000000[console_scripts] guessit = guessit.__main__:main guessit-0.11.0/guessit.egg-info/PKG-INFO0000664000175000017500000006001612572402126020326 0ustar toilaltoilal00000000000000Metadata-Version: 1.1 Name: guessit Version: 0.11.0 Summary: GuessIt - a library for guessing information from video files. Home-page: http://guessit.readthedocs.org/ Author: Nicolas Wack Author-email: wackou@gmail.com License: LGPLv3 Download-URL: https://pypi.python.org/packages/source/g/guessit/guessit-0.11.0.tar.gz Description: GuessIt ======= .. image:: http://img.shields.io/pypi/v/guessit.svg :target: https://pypi.python.org/pypi/guessit :alt: Latest Version .. image:: http://img.shields.io/badge/license-LGPLv3-blue.svg :target: https://pypi.python.org/pypi/guessit :alt: License .. image:: http://img.shields.io/travis/wackou/guessit.svg :target: http://travis-ci.org/wackou/guessit :alt: Build Status .. image:: http://img.shields.io/coveralls/wackou/guessit.svg :target: https://coveralls.io/r/wackou/guessit?branch=master :alt: Coveralls GuessIt is a python library that extracts as much information as possible from a video file. It has a very powerful filename matcher that allows to guess a lot of metadata from a video using its filename only. This matcher works with both movies and tv shows episodes. For example, GuessIt can do the following:: $ guessit "Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi" For: Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi GuessIt found: { [1.00] "mimetype": "video/x-msvideo", [0.80] "episodeNumber": 3, [0.80] "videoCodec": "XviD", [1.00] "container": "avi", [1.00] "format": "HDTV", [0.70] "series": "Treme", [0.50] "title": "Right Place, Wrong Time", [0.80] "releaseGroup": "NoTV", [0.80] "season": 1, [1.00] "type": "episode" } Install ------- Installing GuessIt is simple with `pip `_:: $ pip install guessit or, with `easy_install `_:: $ easy_install guessit But, you really `shouldn't do that `_. You can now launch a demo:: $ guessit -d and guess your own filename:: $ guessit "Breaking.Bad.S05E08.720p.MP4.BDRip.[KoTuWa].mkv" For: Breaking.Bad.S05E08.720p.MP4.BDRip.[KoTuWa].mkv GuessIt found: { [1.00] "mimetype": "video/x-matroska", [1.00] "episodeNumber": 8, [0.30] "container": "mkv", [1.00] "format": "BluRay", [0.70] "series": "Breaking Bad", [1.00] "releaseGroup": "KoTuWa", [1.00] "screenSize": "720p", [1.00] "season": 5, [1.00] "type": "episode" } Filename matcher ---------------- The filename matcher is based on pattern matching and is able to recognize many properties from the filename, like ``title``, ``year``, ``series``, ``episodeNumber``, ``seasonNumber``, ``videoCodec``, ``screenSize``, ``language``. Guessed values are cleaned up and given in a readable format which may not match exactly the raw filename. The full list of available properties can be seen in the `main documentation `_. Other features -------------- GuessIt also allows you to compute a whole lot of hashes from a file, namely all the ones you can find in the hashlib python module (md5, sha1, ...), but also the Media Player Classic hash that is used (amongst others) by OpenSubtitles and SMPlayer, as well as the ed2k hash. If you have the 'guess-language' python package installed, GuessIt can also analyze a subtitle file's contents and detect which language it is written in. If you have the 'enzyme' python package installed, GuessIt can also detect the properties from the actual video file metadata. Usage ----- guessit can be use from command line:: $ guessit usage: guessit [-h] [-t TYPE] [-n] [-c] [-X DISABLED_TRANSFORMERS] [-v] [-P SHOW_PROPERTY] [-u] [-a] [-y] [-f INPUT_FILE] [-d] [-p] [-V] [-s] [--version] [-b] [-i INFO] [-S EXPECTED_SERIES] [-T EXPECTED_TITLE] [-Y] [-D] [-L ALLOWED_LANGUAGES] [-E] [-C ALLOWED_COUNTRIES] [-G EXPECTED_GROUP] [filename [filename ...]] positional arguments: filename Filename or release name to guess optional arguments: -h, --help show this help message and exit Naming: -t TYPE, --type TYPE The suggested file type: movie, episode. If undefined, type will be guessed. -n, --name-only Parse files as name only. Disable folder parsing, extension parsing, and file content analysis. -c, --split-camel Split camel case part of filename. -X DISABLED_TRANSFORMERS, --disabled-transformer DISABLED_TRANSFORMERS Transformer to disable (can be used multiple time) -S EXPECTED_SERIES, --expected-series EXPECTED_SERIES Expected series to parse (can be used multiple times) -T EXPECTED_TITLE, --expected-title EXPECTED_TITLE Expected title (can be used multiple times) -Y, --date-year-first If short date is found, consider the first digits as the year. -D, --date-day-first If short date is found, consider the second digits as the day. -L ALLOWED_LANGUAGES, --allowed-languages ALLOWED_LANGUAGES Allowed language (can be used multiple times) -E, --episode-prefer-number Guess "serie.213.avi" as the episodeNumber 213. Without this option, it will be guessed as season 2, episodeNumber 13 -C ALLOWED_COUNTRIES, --allowed-country ALLOWED_COUNTRIES Allowed country (can be used multiple times) -G EXPECTED_GROUP, --expected-group EXPECTED_GROUP Expected release group (can be used multiple times) Output: -v, --verbose Display debug output -P SHOW_PROPERTY, --show-property SHOW_PROPERTY Display the value of a single property (title, series, videoCodec, year, type ...) -u, --unidentified Display the unidentified parts. -a, --advanced Display advanced information for filename guesses, as json output -y, --yaml Display information for filename guesses as yaml output (like unit-test) -f INPUT_FILE, --input-file INPUT_FILE Read filenames from an input file. -d, --demo Run a few builtin tests instead of analyzing a file Information: -p, --properties Display properties that can be guessed. -V, --values Display property values that can be guessed. -s, --transformers Display transformers that can be used. --version Display the guessit version. guessit.io: -b, --bug Submit a wrong detection to the guessit.io service Other features: -i INFO, --info INFO The desired information type: filename, video, hash_mpc or a hash from python's hashlib module, such as hash_md5, hash_sha1, ...; or a list of any of them, comma-separated It can also be used as a python module:: >>> from guessit import guess_file_info >>> guess_file_info('Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi') {u'mimetype': 'video/x-msvideo', u'episodeNumber': 3, u'videoCodec': u'XviD', u'container': u'avi', u'format': u'HDTV', u'series': u'Treme', u'title': u'Right Place, Wrong Time', u'releaseGroup': u'NoTV', u'season': 1, u'type': u'episode'} Support ------- The project website for GuessIt is hosted at `ReadTheDocs `_. There you will also find the User guide and Developer documentation. This project is hosted on GitHub: ``_ Please report issues and/or feature requests via the `bug tracker `_. You can also report issues using the command-line tool:: $ guessit --bug "filename.that.fails.avi" Contribute ---------- GuessIt is under active development, and contributions are more than welcome! #. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. There is a Contributor Friendly tag for issues that should be ideal for people who are not very familiar with the codebase yet. #. Fork `the repository`_ on Github to start making your changes to the **master** branch (or branch off of it). #. Write a test which shows that the bug was fixed or that the feature works as expected. #. Send a pull request and bug the maintainer until it gets merged and published. :) .. _the repository: https://github.com/wackou/guessit License ------- GuessIt is licensed under the `LGPLv3 license `_. History ======= 0.11.0 (2015-09-04) ------------------- * Fixed year-season episodes with 'x' separator * Fixed name guessing when a subdirectory contains a number * Fixed possible IndexError in release_group plugin * Fixed infinite recursion when multiple languages from same node are ignored in the second pass * Added skip of language guess for 2-3 letters directories * Added exclusion of common words from title guessing * Added a higher confidence on filename over directories 0.10.4 (2015-08-19) ------------------- * Added ``LD``/``MD`` properties * Added better support for ``episodeList`` * Added more rules for filetype autodetection * Added support for ``episodeList`` on weak episode patterns * Added ``partList`` property (list for ``part`` property) * Added vob to supported file extensions * Added more ignore words to language detection * Added string options support for API methods (will be parsed like command-line) * Added better subtitle detection (prefix priority over suffix) * Fixed ``version`` property no detected when detached from ``episodeNumber`` * Fixed ``releaseGroup`` property no detected when prefixed by ``screenSize`` * Fixed single digit detected as an ``episodeNumber`` * Fixed an internal issue in matcher causing absolute and relative group spans confusion * Fixed an internal issue in properties container causing invalid ordering of found patterns * Fixed raw value for some properties (--advanced) * Use pytest as test runner * Remove support for python 2.6 0.10.3 (2015-04-04) ------------------- * Fix issues related to unicode encoding/decoding * Fix possible crashes in guess_video_rexps * Fix invalid guess result when crc32 contains 6 digits than can be parsed as a date 0.10.2 (2015-03-08) ------------------- * Use common words to resolve conflicts on strings * Bump babelfish version * Fix setuptools deprecation warning * Package argparse dependency only if python<2.7 0.10.1 (2015-01-05) ------------------- * Avoid word Stay to be recognized as AY subtitle * Fixed exception when no unidentified leaves remains * Avoid usage of deprecated EntryPoint.load() require argument * Fixed invalid raw data for some properties (title, series and maybe others) 0.10.0 (2014-12-27) ------------------- * Fixed exception when serie title starts with Ep * Fixed exception when trying to parse a full length country name * Removed deprecated optparse module, replaced by argparse 0.9.4 (2014-11-10) ------------------ * Fixed exception when filename contains multiple languages ISO codes * Fixed transformers initialization logging * Fixed possible exception in language transformer * Added more words to common english words 0.9.3 (2014-09-14) ------------------ * Added ``Preair`` and ``Remux`` to ``other`` property * Better detection of ``audioProfile`` = ``HD`` / ``HDMA`` for ``audioCodec`` = ``DTS`` * Better detection of ``format``` = ``BluRay`` (when followed by Rip) * Recognize ``RC`` as ``R5`` * Recognize ``WEB-HD```and ``ẀEB`` as ``WEB-DL`` 0.9.2 (2014-09-13) ------------------ * Added support of option registration on transformers * Better detection of ``releaseGroup`` when using ``expected-series`` or ``expected-title`` option * Better ``audioChannel`` = ``5.1`` / ``7.1`` guessing (``6ch``, ``8ch``) * Fixed usage not showing when invalid options were passed * Added ``PAL``, ``SECAM`` and ``NTSC`` to ``other`` possible values * Recognize DVD-9 and DVD-5 as ``format`` = ``DVD`` property 0.9.1 (2014-09-06) ------------------ * Added ``--unidentified`` option to display unidentified parts of the filename This option affects command line only - From API `unidentified` properties will always be grabbed regardless this settings * Better guessing of ``releaseGroup`` property * Added ``mHD`` and ``HDLight`` to ``other properties`` * Better guessing of ``format`` = ``DVD`` property (DVD-R pattern) * Some ``info`` logs changed to ``debug`` for quiet integration * Small fixes 0.9.0 (2014-09-05) ------------------ * Better auto-detection of anime episodes, containing a ``crc32`` or a digits ``episodeNumber``. * Better listing of options on ``guessit -h`` * Added ``--allowed-countries`` and ``--allowed-languages`` to avoid two or three letters words to be guessed as ``country`` or ``language`` * Added ``--disabled-transformers`` option to disable transformer plugin at runtime. * Added ``--episode-prefer-number`` option, for ``guess -t episode 'serie.123.avi'`` to return ``episodeNumber`` = ``123`` instead of ``season`` = ``1`` + ``episodeNumber`` = 23`` * Added ``--split-camel`` option (now disabled by default) * Added ``episodeCount`` and ``seasonCount`` properties (x-of-n notation) * Added ``--date-year-first``` and ``--date-day-first`` options * Added ``--expected-title``, ``--expected-series`` and ``--expected-groups`` to help finding values when those properties are known * Added ``10bit`` value to ``videoProfile`` * Added ``--show-property`` option to only show a single property * Added ``--input-file`` option to parse a list of * Added ``--version`` option * Added ``ass```to subtitle extensions * Added ``Fansub`` value for ``other`` property * Added more date formats support with ``dateutil`` dependency * Added customizable ``clean_function`` (API) * Added ``default_options`` (API) * Fixed ``--yaml`` option to support ``language`` and ``country`` * Fixed ``transformers.add_transformer()`` function (API) 0.8 (2014-07-06) ---------------- * New webservice that allows to use GuessIt just by sending a POST request to the http://guessit.io/guess url * Command-line util can now report bugs to the http://guessit.io/bugs service by specifying the ``-b`` or ``--bug`` flag * GuessIt can now use the Enzyme python package to detect metadata out of the actual video file metadata instead of the filename * Finished transition to ``babelfish.Language`` and ``babelfish.Country`` * New property: ``duration`` which returns the duration of the video in seconds This requires the Enzyme package to work * New property: ``fileSize`` which returns the size of the file in bytes * Renamed property ``special`` to ``episodeDetails`` * Added support for Python 3.4 * Optimization and bugfixes 0.7.1 (2014-03-03) ------------------ * New property "special": values can be trailer, pilot, unaired * New options for the guessit cmdline util: ``-y``, ``--yaml`` outputs the result in yaml format and ``-n``, ``--name-only`` analyzes the input as simple text (instead of filename) * Added properties formatters and validators * Removed support for python 3.2 * A healthy amount of code cleanup/refactoring and fixes :) 0.7 (2014-01-29) ---------------- * New plugin API that allows to register custom patterns / transformers * Uses Babelfish for language and country detection * Added Quality API to rate file quality from guessed property values * Better and more accurate overall detection * Added roman and word numeral detection * Added 'videoProfile' and 'audioProfile' property * Moved boolean properties to 'other' property value ('is3D' became 'other' = '3D') * Added more possible values for various properties. * Added command line option to list available properties and values * Fixes for Python3 support 0.6.2 (2013-11-08) ------------------ * Added support for nfo files * GuessIt can now output advanced information as json ('-a' on the command line) * Better language detection * Added new property: 'is3D' 0.6.1 (2013-09-18) ------------------ * New property "idNumber" that tries to identify a hash value or a serial number * The usual bugfixes 0.6 (2013-07-16) ---------------- * Better packaging: unittests and doc included in source tarball * Fixes everywhere: unicode, release group detection, language detection, ... * A few speed optimizations 0.5.4 (2013-02-11) ------------------ * guessit can be installed as a system wide script (thanks @dplarson) * Enhanced logging facilities * Fixes for episode number and country detection 0.5.3 (2012-11-01) ------------------ * GuessIt can now optionally act as a wrapper around the 'guess-language' python module, and thus provide detection of the natural language in which a body of text is written * Lots of fixes everywhere, mostly for properties and release group detection 0.5.2 (2012-10-02) ------------------ * Much improved auto-detection of filetype * Fixed some issues with the detection of release groups 0.5.1 (2012-09-23) ------------------ * now detects 'country' property; also detect 'year' property for series * more patterns and bugfixes 0.5 (2012-07-29) ---------------- * Python3 compatibility * the usual assortment of bugfixes 0.4.2 (2012-05-19) ------------------ * added Language.tmdb language code property for TheMovieDB * added ability to recognize list of episodes * bugfixes for Language.__nonzero__ and episode regexps 0.4.1 (2012-05-12) ------------------ * bugfixes for unicode, paths on Windows, autodetection, and language issues 0.4 (2012-04-28) ---------------- * much improved language detection, now also detect language variants * supports more video filetypes (thanks to Rob McMullen) 0.3.1 (2012-03-15) ------------------ * fixed package installation from PyPI * better imports for the transformations (thanks Diaoul!) * some small language fixes 0.3 (2012-03-12) ---------------- * fix to recognize 1080p format (thanks to Jonathan Lauwers) 0.3b2 (2012-03-02) ------------------ * fixed the package installation 0.3b1 (2012-03-01) ------------------ * refactored quite a bit, code is much cleaner now * fixed quite a few tests * re-vamped the documentation, wrote some more 0.2 (2011-05-27) ---------------- * new parser/matcher completely replaced the old one * quite a few more unittests and fixes 0.2b1 (2011-05-20) ------------------ * brand new parser/matcher that is much more flexible and powerful * lots of cleaning and a bunch of unittests 0.1 (2011-05-10) ---------------- * fixed a few minor issues & heuristics 0.1b2 (2011-03-12) ------------------ * Added PyPI trove classifiers * fixed version number in setup.py 0.1b1 (2011-03-12) ------------------ * first pre-release version; imported from Smewt with a few enhancements already in there. Keywords: smewt media video metadata python library Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Operating System :: OS Independent Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Multimedia Classifier: Topic :: Software Development :: Libraries :: Python Modules guessit-0.11.0/guessit.egg-info/SOURCES.txt0000664000175000017500000000606312572402126021117 0ustar toilaltoilal00000000000000HISTORY.rst LICENSE MANIFEST.in README.rst setup.cfg setup.py docs/conf.py docs/index.rst docs/presentation.rst docs/projectinfo.rst docs/_static/guessit-logo.png docs/_static/lgplv3-88x31.png docs/_static/coinwidget/icon_bitcoin.png docs/_static/coinwidget/icon_litecoin.png docs/_static/coinwidget/icon_qrcode.png docs/_static/coinwidget/icon_wallet.png docs/_templates/sidebarintro.html docs/_templates/sidebarlogo.html docs/_themes/.gitignore docs/_themes/LICENSE docs/_themes/README.rst docs/_themes/flask_theme_support.py docs/_themes/flask_theme_support.pyc docs/_themes/__pycache__/flask_theme_support.cpython-34.pyc docs/_themes/kr/layout.html docs/_themes/kr/relations.html docs/_themes/kr/theme.conf docs/_themes/kr/static/flasky.css_t docs/_themes/kr_small/layout.html docs/_themes/kr_small/theme.conf docs/_themes/kr_small/static/flasky.css_t docs/api/guess.rst docs/api/matcher.rst docs/api/matchtree.rst docs/dev/internals.rst docs/user/commandline.rst docs/user/install.rst docs/user/properties.rst docs/user/python.rst guessit/__init__.py guessit/__main__.py guessit/__version__.py guessit/containers.py guessit/date.py guessit/fileutils.py guessit/guess.py guessit/hash_ed2k.py guessit/hash_mpc.py guessit/language.py guessit/matcher.py guessit/matchtree.py guessit/options.py guessit/quality.py guessit/slogging.py guessit/textutils.py guessit/tlds-alpha-by-domain.txt guessit.egg-info/PKG-INFO guessit.egg-info/SOURCES.txt guessit.egg-info/dependency_links.txt guessit.egg-info/entry_points.txt guessit.egg-info/pbr.json guessit.egg-info/requires.txt guessit.egg-info/top_level.txt guessit/patterns/__init__.py guessit/patterns/extension.py guessit/patterns/list.py guessit/patterns/numeral.py guessit/plugins/__init__.py guessit/plugins/transformers.py guessit/test/__init__.py guessit/test/autodetect.yaml guessit/test/dummy.srt guessit/test/episodes.yaml guessit/test/guessittest.py guessit/test/movies.yaml guessit/test/opensubtitles_languages_2012_05_09.txt guessit/test/test_api.py guessit/test/test_autodetect.py guessit/test/test_autodetect_all.py guessit/test/test_episode.py guessit/test/test_hashes.py guessit/test/test_language.py guessit/test/test_main.py guessit/test/test_matchtree.py guessit/test/test_movie.py guessit/test/test_quality.py guessit/test/test_utils.py guessit/transfo/__init__.py guessit/transfo/expected_series.py guessit/transfo/expected_title.py guessit/transfo/guess_bonus_features.py guessit/transfo/guess_country.py guessit/transfo/guess_date.py guessit/transfo/guess_episode_details.py guessit/transfo/guess_episode_info_from_position.py guessit/transfo/guess_episodes_rexps.py guessit/transfo/guess_filetype.py guessit/transfo/guess_idnumber.py guessit/transfo/guess_language.py guessit/transfo/guess_movie_title_from_position.py guessit/transfo/guess_properties.py guessit/transfo/guess_release_group.py guessit/transfo/guess_video_rexps.py guessit/transfo/guess_weak_episodes_rexps.py guessit/transfo/guess_website.py guessit/transfo/guess_year.py guessit/transfo/split_explicit_groups.py guessit/transfo/split_on_dash.py guessit/transfo/split_path_components.pyguessit-0.11.0/guessit.egg-info/pbr.json0000664000175000017500000000005712572402126020706 0ustar toilaltoilal00000000000000{"git_version": "da60e4b", "is_release": false}guessit-0.11.0/guessit.egg-info/requires.txt0000664000175000017500000000020212572402126021620 0ustar toilaltoilal00000000000000babelfish>=0.5.4 stevedore>=0.14 requests python-dateutil>=2.1 [language_detection] guess-language>=0.2 [video_metadata] enzyme guessit-0.11.0/guessit.egg-info/dependency_links.txt0000664000175000017500000000000112572402126023274 0ustar toilaltoilal00000000000000 guessit-0.11.0/guessit.egg-info/top_level.txt0000664000175000017500000000001012572402126021747 0ustar toilaltoilal00000000000000guessit guessit-0.11.0/guessit/0000775000175000017500000000000012572402126015534 5ustar toilaltoilal00000000000000guessit-0.11.0/guessit/hash_mpc.py0000664000175000017500000000357512451755071017710 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import struct import os def hash_file(filename): """This function is taken from: http://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes and is licensed under the GPL.""" longlongformat = b'q' # long long bytesize = struct.calcsize(longlongformat) f = open(filename, "rb") filesize = os.path.getsize(filename) hash_value = filesize if filesize < 65536 * 2: raise Exception("SizeError: size is %d, should be > 132K..." % filesize) for x in range(int(65536 / bytesize)): buf = f.read(bytesize) (l_value,) = struct.unpack(longlongformat, buf) hash_value += l_value hash_value &= 0xFFFFFFFFFFFFFFFF # to remain as 64bit number f.seek(max(0, filesize - 65536), 0) for x in range(int(65536 / bytesize)): buf = f.read(bytesize) (l_value,) = struct.unpack(longlongformat, buf) hash_value += l_value hash_value &= 0xFFFFFFFFFFFFFFFF f.close() return "%016x" % hash_value guessit-0.11.0/guessit/options.py0000664000175000017500000001070312451755071017610 0ustar toilaltoilal00000000000000from argparse import ArgumentParser def build_opts(transformers=None): opts = ArgumentParser() opts.add_argument(dest='filename', help='Filename or release name to guess', nargs='*') naming_opts = opts.add_argument_group("Naming") naming_opts.add_argument('-t', '--type', dest='type', default=None, help='The suggested file type: movie, episode. If undefined, type will be guessed.') naming_opts.add_argument('-n', '--name-only', dest='name_only', action='store_true', default=False, help='Parse files as name only. Disable folder parsing, extension parsing, and file content analysis.') naming_opts.add_argument('-c', '--split-camel', dest='split_camel', action='store_true', default=False, help='Split camel case part of filename.') naming_opts.add_argument('-X', '--disabled-transformer', action='append', dest='disabled_transformers', help='Transformer to disable (can be used multiple time)') output_opts = opts.add_argument_group("Output") output_opts.add_argument('-v', '--verbose', action='store_true', dest='verbose', default=False, help='Display debug output') output_opts.add_argument('-P', '--show-property', dest='show_property', default=None, help='Display the value of a single property (title, series, videoCodec, year, type ...)'), output_opts.add_argument('-u', '--unidentified', dest='unidentified', action='store_true', default=False, help='Display the unidentified parts.'), output_opts.add_argument('-a', '--advanced', dest='advanced', action='store_true', default=False, help='Display advanced information for filename guesses, as json output') output_opts.add_argument('-y', '--yaml', dest='yaml', action='store_true', default=False, help='Display information for filename guesses as yaml output (like unit-test)') output_opts.add_argument('-f', '--input-file', dest='input_file', default=False, help='Read filenames from an input file.') output_opts.add_argument('-d', '--demo', action='store_true', dest='demo', default=False, help='Run a few builtin tests instead of analyzing a file') information_opts = opts.add_argument_group("Information") information_opts.add_argument('-p', '--properties', dest='properties', action='store_true', default=False, help='Display properties that can be guessed.') information_opts.add_argument('-V', '--values', dest='values', action='store_true', default=False, help='Display property values that can be guessed.') information_opts.add_argument('-s', '--transformers', dest='transformers', action='store_true', default=False, help='Display transformers that can be used.') information_opts.add_argument('--version', dest='version', action='store_true', default=False, help='Display the guessit version.') webservice_opts = opts.add_argument_group("guessit.io") webservice_opts.add_argument('-b', '--bug', action='store_true', dest='submit_bug', default=False, help='Submit a wrong detection to the guessit.io service') other_opts = opts.add_argument_group("Other features") other_opts.add_argument('-i', '--info', dest='info', default='filename', help='The desired information type: filename, video, hash_mpc or a hash from python\'s ' 'hashlib module, such as hash_md5, hash_sha1, ...; or a list of any of ' 'them, comma-separated') if transformers: for transformer in transformers: transformer.register_arguments(opts, naming_opts, output_opts, information_opts, webservice_opts, other_opts) return opts, naming_opts, output_opts, information_opts, webservice_opts, other_opts _opts, _naming_opts, _output_opts, _information_opts, _webservice_opts, _other_opts = None, None, None, None, None, None def reload(transformers=None): global _opts, _naming_opts, _output_opts, _information_opts, _webservice_opts, _other_opts _opts, _naming_opts, _output_opts, _information_opts, _webservice_opts, _other_opts = build_opts(transformers) def get_opts(): return _opts guessit-0.11.0/guessit/plugins/0000775000175000017500000000000012572402126017215 5ustar toilaltoilal00000000000000guessit-0.11.0/guessit/plugins/__init__.py0000664000175000017500000000160012410027040021310 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals guessit-0.11.0/guessit/plugins/transformers.py0000664000175000017500000002255412534564763022342 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from logging import getLogger from pkg_resources import EntryPoint from guessit.options import reload as reload_options from stevedore import ExtensionManager from stevedore.extension import Extension log = getLogger(__name__) class Transformer(object): # pragma: no cover def __init__(self, priority=0): self.priority = priority self.log = getLogger(self.name) @property def name(self): return self.__class__.__name__ def supported_properties(self): return {} def second_pass_options(self, mtree, options=None): return None def should_process(self, mtree, options=None): return True def process(self, mtree, options=None): pass def post_process(self, mtree, options=None): pass def register_arguments(self, opts, naming_opts, output_opts, information_opts, webservice_opts, other_options): pass def rate_quality(self, guess, *props): return 0 class CustomTransformerExtensionManager(ExtensionManager): def __init__(self, namespace='guessit.transformer', invoke_on_load=True, invoke_args=(), invoke_kwds={}, propagate_map_exceptions=True, on_load_failure_callback=None, verify_requirements=False): super(CustomTransformerExtensionManager, self).__init__(namespace=namespace, invoke_on_load=invoke_on_load, invoke_args=invoke_args, invoke_kwds=invoke_kwds, propagate_map_exceptions=propagate_map_exceptions, on_load_failure_callback=on_load_failure_callback, verify_requirements=verify_requirements) @staticmethod def order_extensions(extensions): """Order the loaded transformers It should follow those rules - website before language (eg: tvu.org.ru vs russian) - language before episodes_rexps - properties before language (eg: he-aac vs hebrew) - release_group before properties (eg: XviD-?? vs xvid) """ extensions.sort(key=lambda ext: -ext.obj.priority) return extensions @staticmethod def _load_one_plugin(ep, invoke_on_load, invoke_args, invoke_kwds, verify_requirements=True): if not ep.dist: # `require` argument of ep.load() is deprecated in newer versions of setuptools if hasattr(ep, 'resolve'): plugin = ep.resolve() elif hasattr(ep, '_load'): plugin = ep._load() else: plugin = ep.load(require=False) else: plugin = ep.load() if invoke_on_load: obj = plugin(*invoke_args, **invoke_kwds) else: obj = None return Extension(ep.name, ep, plugin, obj) def _load_plugins(self, invoke_on_load, invoke_args, invoke_kwds, verify_requirements): return self.order_extensions(super(CustomTransformerExtensionManager, self)._load_plugins(invoke_on_load, invoke_args, invoke_kwds, verify_requirements)) def objects(self): return self.map(self._get_obj) @staticmethod def _get_obj(ext): return ext.obj def object(self, name): try: return self[name].obj except KeyError: return None def register_module(self, name=None, module_name=None, attrs=(), entry_point=None): if entry_point: ep = EntryPoint.parse(entry_point) else: ep = EntryPoint(name, module_name, attrs) loaded = self._load_one_plugin(ep, invoke_on_load=True, invoke_args=(), invoke_kwds={}) if loaded: self.extensions.append(loaded) self.extensions = self.order_extensions(self.extensions) self._extensions_by_name = None class DefaultTransformerExtensionManager(CustomTransformerExtensionManager): @property def _internal_entry_points(self): return ['split_path_components = guessit.transfo.split_path_components:SplitPathComponents', 'guess_filetype = guessit.transfo.guess_filetype:GuessFiletype', 'split_explicit_groups = guessit.transfo.split_explicit_groups:SplitExplicitGroups', 'guess_date = guessit.transfo.guess_date:GuessDate', 'guess_website = guessit.transfo.guess_website:GuessWebsite', 'guess_release_group = guessit.transfo.guess_release_group:GuessReleaseGroup', 'guess_properties = guessit.transfo.guess_properties:GuessProperties', 'guess_language = guessit.transfo.guess_language:GuessLanguage', 'guess_video_rexps = guessit.transfo.guess_video_rexps:GuessVideoRexps', 'guess_episodes_rexps = guessit.transfo.guess_episodes_rexps:GuessEpisodesRexps', 'guess_weak_episodes_rexps = guessit.transfo.guess_weak_episodes_rexps:GuessWeakEpisodesRexps', 'guess_bonus_features = guessit.transfo.guess_bonus_features:GuessBonusFeatures', 'guess_year = guessit.transfo.guess_year:GuessYear', 'guess_country = guessit.transfo.guess_country:GuessCountry', 'guess_idnumber = guessit.transfo.guess_idnumber:GuessIdnumber', 'split_on_dash = guessit.transfo.split_on_dash:SplitOnDash', 'guess_episode_info_from_position = guessit.transfo.guess_episode_info_from_position:GuessEpisodeInfoFromPosition', 'guess_movie_title_from_position = guessit.transfo.guess_movie_title_from_position:GuessMovieTitleFromPosition', 'guess_episode_details = guessit.transfo.guess_episode_details:GuessEpisodeDetails', 'expected_series = guessit.transfo.expected_series:ExpectedSeries', 'expected_title = guessit.transfo.expected_title:ExpectedTitle',] def _find_entry_points(self, namespace): entry_points = {} # Internal entry points if namespace == self.namespace: for internal_entry_point_str in self._internal_entry_points: internal_entry_point = EntryPoint.parse(internal_entry_point_str) entry_points[internal_entry_point.name] = internal_entry_point # Package entry points setuptools_entrypoints = super(DefaultTransformerExtensionManager, self)._find_entry_points(namespace) for setuptools_entrypoint in setuptools_entrypoints: entry_points[setuptools_entrypoint.name] = setuptools_entrypoint return list(entry_points.values()) _extensions = None def all_transformers(): return _extensions.objects() def get_transformer(name): return _extensions.object(name) def add_transformer(name, module_name, class_name): """ Add a transformer :param name: the name of the transformer. ie: 'guess_regexp_id' :param name: the module name. ie: 'flexget.utils.parsers.transformers.guess_regexp_id' :param class_name: the class name. ie: 'GuessRegexpId' """ _extensions.register_module(name, module_name, (class_name,)) def add_transformer(entry_point): """ Add a transformer :param entry_point: entry point spec format. ie: 'guess_regexp_id = flexget.utils.parsers.transformers.guess_regexp_id:GuessRegexpId' """ _extensions.register_module(entry_point = entry_point) def reload(custom=False): """ Reload extension manager with default or custom one. :param custom: if True, custom manager will be used, else default one. Default manager will load default extensions from guessit and setuptools packaging extensions Custom manager will not load default extensions from guessit, using only setuptools packaging extensions. :type custom: boolean """ global _extensions if custom: _extensions = CustomTransformerExtensionManager() else: _extensions = DefaultTransformerExtensionManager() reload_options(all_transformers()) reload() guessit-0.11.0/guessit/patterns/0000775000175000017500000000000012572402126017374 5ustar toilaltoilal00000000000000guessit-0.11.0/guessit/patterns/list.py0000664000175000017500000000655712571103570020736 0ustar toilaltoilal00000000000000import re from guessit.patterns import sep, build_or_pattern from guessit.patterns.numeral import parse_numeral range_separators = ['-', 'to', 'a'] discrete_separators = ['&', 'and', 'et'] excluded_separators = ['.'] # Dot cannot serve as a discrete_separator discrete_sep = sep for range_separator in range_separators: discrete_sep = discrete_sep.replace(range_separator, '') for excluded_separator in excluded_separators: discrete_sep = discrete_sep.replace(excluded_separator, '') discrete_separators.append(discrete_sep) all_separators = list(range_separators) all_separators.extend(discrete_separators) range_separators_re = re.compile(build_or_pattern(range_separators), re.IGNORECASE) discrete_separators_re = re.compile(build_or_pattern(discrete_separators), re.IGNORECASE) all_separators_re = re.compile(build_or_pattern(all_separators), re.IGNORECASE) def list_parser(value, property_list_name, discrete_separators_re=discrete_separators_re, range_separators_re=range_separators_re, allow_discrete=False, fill_gaps=False): discrete_elements = filter(lambda x: x != '', discrete_separators_re.split(value)) discrete_elements = [x.strip() for x in discrete_elements] proper_discrete_elements = [] i = 0 while i < len(discrete_elements): if i < len(discrete_elements) - 2 and range_separators_re.match(discrete_elements[i+1]): proper_discrete_elements.append(discrete_elements[i] + discrete_elements[i+1] + discrete_elements[i+2]) i += 3 else: match = range_separators_re.search(discrete_elements[i]) if match and match.start() == 0: proper_discrete_elements[i - 1] += discrete_elements[i] elif match and match.end() == len(discrete_elements[i]): proper_discrete_elements.append(discrete_elements[i] + discrete_elements[i + 1]) else: proper_discrete_elements.append(discrete_elements[i]) i += 1 discrete_elements = proper_discrete_elements ret = [] for discrete_element in discrete_elements: range_values = filter(lambda x: x != '', range_separators_re.split(discrete_element)) range_values = [x.strip() for x in range_values] if len(range_values) > 1: for x in range(0, len(range_values) - 1): start_range_ep = parse_numeral(range_values[x]) end_range_ep = parse_numeral(range_values[x+1]) for range_ep in range(start_range_ep, end_range_ep + 1): if range_ep not in ret: ret.append(range_ep) else: discrete_value = parse_numeral(discrete_element) if discrete_value not in ret: ret.append(discrete_value) if len(ret) > 1: if not allow_discrete: valid_ret = list() # replace discrete elements by ranges valid_ret.append(ret[0]) for i in range(0, len(ret) - 1): previous = valid_ret[len(valid_ret) - 1] if ret[i+1] < previous: pass else: valid_ret.append(ret[i+1]) ret = valid_ret if fill_gaps: ret = list(range(min(ret), max(ret) + 1)) if len(ret) > 1: return {None: ret[0], property_list_name: ret} if len(ret) > 0: return ret[0] return Noneguessit-0.11.0/guessit/patterns/__init__.py0000775000175000017500000000437312534564763021535 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # Copyright (c) 2013 Rémi Alvergnat # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import re group_delimiters = ['()', '[]', '{}'] # separator character regexp sep = r'[][,)(}:{+ /~/\._-]' # regexp art, hehe :D _dash = '-' _psep = '[\W_]?' def build_or_pattern(patterns, escape=False): """Build a or pattern string from a list of possible patterns """ or_pattern = [] for pattern in patterns: if not or_pattern: or_pattern.append('(?:') else: or_pattern.append('|') or_pattern.append('(?:%s)' % re.escape(pattern) if escape else pattern) or_pattern.append(')') return ''.join(or_pattern) def compile_pattern(pattern, enhance=True): """Compile and enhance a pattern :param pattern: Pattern to compile (regexp). :type pattern: string :param pattern: Enhance pattern before compiling. :type pattern: string :return: The compiled pattern :rtype: regular expression object """ return re.compile(enhance_pattern(pattern) if enhance else pattern, re.IGNORECASE) def enhance_pattern(pattern): """Enhance pattern to match more equivalent values. '-' are replaced by '[\W_]?', which matches more types of separators (or none) :param pattern: Pattern to enhance (regexp). :type pattern: string :return: The enhanced pattern :rtype: string """ return pattern.replace(_dash, _psep) guessit-0.11.0/guessit/patterns/numeral.py0000664000175000017500000001135412451755071021423 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Rémi Alvergnat # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import re digital_numeral = '\d{1,4}' roman_numeral = "(?=[MCDLXVI]+)M{0,4}(?:CM|CD|D?C{0,3})(?:XC|XL|L?X{0,3})(?:IX|IV|V?I{0,3})" english_word_numeral_list = [ 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen', 'twenty' ] french_word_numeral_list = [ 'zéro', 'un', 'deux', 'trois', 'quatre', 'cinq', 'six', 'sept', 'huit', 'neuf', 'dix', 'onze', 'douze', 'treize', 'quatorze', 'quinze', 'seize', 'dix-sept', 'dix-huit', 'dix-neuf', 'vingt' ] french_alt_word_numeral_list = [ 'zero', 'une', 'deux', 'trois', 'quatre', 'cinq', 'six', 'sept', 'huit', 'neuf', 'dix', 'onze', 'douze', 'treize', 'quatorze', 'quinze', 'seize', 'dixsept', 'dixhuit', 'dixneuf', 'vingt' ] def __build_word_numeral(*args, **kwargs): re_ = None for word_list in args: for word in word_list: if not re_: re_ = '(?:(?=\w+)' else: re_ += '|' re_ += word re_ += ')' return re_ word_numeral = __build_word_numeral(english_word_numeral_list, french_word_numeral_list, french_alt_word_numeral_list) numeral = '(?:' + digital_numeral + '|' + roman_numeral + '|' + word_numeral + ')' __romanNumeralMap = ( ('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100), ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), ('IX', 9), ('V', 5), ('IV', 4), ('I', 1) ) __romanNumeralPattern = re.compile('^' + roman_numeral + '$') def __parse_roman(value): """convert Roman numeral to integer""" if not __romanNumeralPattern.search(value): raise ValueError('Invalid Roman numeral: %s' % value) result = 0 index = 0 for num, integer in __romanNumeralMap: while value[index:index + len(num)] == num: result += integer index += len(num) return result def __parse_word(value): """Convert Word numeral to integer""" for word_list in [english_word_numeral_list, french_word_numeral_list, french_alt_word_numeral_list]: try: return word_list.index(value.lower()) except ValueError: pass raise ValueError _clean_re = re.compile('[^\d]*(\d+)[^\d]*') def parse_numeral(value, int_enabled=True, roman_enabled=True, word_enabled=True, clean=True): """Parse a numeric value into integer. input can be an integer as a string, a roman numeral or a word :param value: Value to parse. Can be an integer, roman numeral or word. :type value: string :return: Numeric value, or None if value can't be parsed :rtype: int """ if int_enabled: try: if clean: match = _clean_re.match(value) if match: clean_value = match.group(1) return int(clean_value) return int(value) except ValueError: pass if roman_enabled: try: if clean: for word in value.split(): try: return __parse_roman(word.upper()) except ValueError: pass return __parse_roman(value) except ValueError: pass if word_enabled: try: if clean: for word in value.split(): try: return __parse_word(word) except ValueError: pass return __parse_word(value) except ValueError: pass raise ValueError('Invalid numeral: ' + value) guessit-0.11.0/guessit/patterns/extension.py0000664000175000017500000000251212553451003021757 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # Copyright (c) 2013 Rémi Alvergnat # Copyright (c) 2011 Ricard Marxer # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals subtitle_exts = ['srt', 'idx', 'sub', 'ssa', 'ass'] info_exts = ['nfo'] video_exts = ['3g2', '3gp', '3gp2', 'asf', 'avi', 'divx', 'flv', 'm4v', 'mk2', 'mka', 'mkv', 'mov', 'mp4', 'mp4a', 'mpeg', 'mpg', 'ogg', 'ogm', 'ogv', 'qt', 'ra', 'ram', 'rm', 'ts', 'wav', 'webm', 'wma', 'wmv', 'iso', 'vob'] guessit-0.11.0/guessit/guess.py0000664000175000017500000004132712571104227017243 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import json import datetime import logging from guessit import UnicodeMixin, s, u, base_text_type from babelfish import Language, Country from guessit.textutils import common_words log = logging.getLogger(__name__) class GuessMetadata(object): """GuessMetadata contains confidence, an input string, span and related property. If defined on a property of Guess object, it overrides the object defined as global. :param parent: The parent metadata, used for undefined properties in self object :type parent: :class: `GuessMedata` :param confidence: The confidence (from 0.0 to 1.0) :type confidence: number :param input: The input string :type input: string :param span: The input string :type span: tuple (int, int) :param prop: The found property definition :type prop: :class `guessit.containers._Property` """ def __init__(self, parent=None, confidence=None, input=None, span=None, prop=None, *args, **kwargs): self.parent = parent if confidence is None and self.parent is None: self._confidence = 1.0 else: self._confidence = confidence self._input = input self._span = span self._prop = prop @property def confidence(self): """The confidence :rtype: int :return: confidence value """ return self._confidence if self._confidence is not None else self.parent.confidence if self.parent else None @confidence.setter def confidence(self, confidence): self._confidence = confidence @property def input(self): """The input :rtype: string :return: String used to find this guess value """ return self._input if self._input is not None else self.parent.input if self.parent else None @input.setter def input(self, input): """The input :rtype: string """ self._input = input @property def span(self): """The span :rtype: tuple (int, int) :return: span of input string used to find this guess value """ return self._span if self._span is not None else self.parent.span if self.parent else None @span.setter def span(self, span): """The span :rtype: tuple (int, int) :return: span of input string used to find this guess value """ self._span = span @property def prop(self): """The property :rtype: :class:`_Property` :return: The property """ return self._prop if self._prop is not None else self.parent.prop if self.parent else None @property def raw(self): """Return the raw information (original match from the string, not the cleaned version) associated with the given property name.""" if self.input and self.span: return self.input[self.span[0]:self.span[1]] return None def __repr__(self, *args, **kwargs): return object.__repr__(self, *args, **kwargs) def _split_kwargs(**kwargs): metadata_args = {} for prop in dir(GuessMetadata): try: metadata_args[prop] = kwargs.pop(prop) except KeyError: pass return metadata_args, kwargs class Guess(UnicodeMixin, dict): """A Guess is a dictionary which has an associated confidence for each of its values. As it is a subclass of dict, you can use it everywhere you expect a simple dict.""" def __init__(self, *args, **kwargs): metadata_kwargs, kwargs = _split_kwargs(**kwargs) self._global_metadata = GuessMetadata(**metadata_kwargs) dict.__init__(self, *args, **kwargs) self._metadata = {} for prop in self: self._metadata[prop] = GuessMetadata(parent=self._global_metadata) def rename(self, old_name, new_name): if old_name in self._metadata: metadata = self._metadata[old_name] del self._metadata[old_name] self._metadata[new_name] = metadata if old_name in self: value = self[old_name] del self[old_name] self[new_name] = value return True return False def to_dict(self, advanced=False): """Return the guess as a dict containing only base types, ie: where dates, languages, countries, etc. are converted to strings. if advanced is True, return the data as a json string containing also the raw information of the properties.""" data = dict(self) for prop, value in data.items(): if isinstance(value, datetime.date): data[prop] = value.isoformat() elif isinstance(value, (UnicodeMixin, base_text_type)): data[prop] = u(value) elif isinstance(value, (Language, Country)): data[prop] = value.guessit elif isinstance(value, list): data[prop] = [u(x) for x in value] if advanced: metadata = self.metadata(prop) prop_data = {'value': data[prop]} if metadata.raw: prop_data['raw'] = metadata.raw if metadata.confidence: prop_data['confidence'] = metadata.confidence data[prop] = prop_data return data def nice_string(self, advanced=False): """Return a string with the property names and their values, that also displays the associated confidence to each property. FIXME: doc with param""" if advanced: data = self.to_dict(advanced) return json.dumps(data, indent=4, ensure_ascii=False) else: data = self.to_dict() parts = json.dumps(data, indent=4, ensure_ascii=False).split('\n') for i, p in enumerate(parts): if p[:5] != ' "': continue prop = p.split('"')[1] parts[i] = (' [%.2f] "' % self.confidence(prop)) + p[5:] return '\n'.join(parts) def __unicode__(self): return u(self.to_dict()) def metadata(self, prop=None): """Return the metadata associated with the given property name If no property name is given, get the global_metadata """ if prop is None: return self._global_metadata if prop not in self._metadata: self._metadata[prop] = GuessMetadata(parent=self._global_metadata) return self._metadata[prop] def confidence(self, prop=None): return self.metadata(prop).confidence def set_confidence(self, prop, confidence): self.metadata(prop).confidence = confidence def raw(self, prop): return self.metadata(prop).raw def set(self, prop_name, value, *args, **kwargs): if value is None: try: del self[prop_name] except KeyError: pass try: del self._metadata[prop_name] except KeyError: pass else: self[prop_name] = value if 'metadata' in kwargs.keys(): self._metadata[prop_name] = kwargs['metadata'] else: self._metadata[prop_name] = GuessMetadata(parent=self._global_metadata, *args, **kwargs) def update(self, other, confidence=None): dict.update(self, other) if isinstance(other, Guess): for prop in other: try: self._metadata[prop] = other._metadata[prop] except KeyError: pass if confidence is not None: for prop in other: self.set_confidence(prop, confidence) def update_highest_confidence(self, other): """Update this guess with the values from the given one. In case there is property present in both, only the one with the highest one is kept.""" if not isinstance(other, Guess): raise ValueError('Can only call this function on Guess instances') for prop in other: if prop in self and self.metadata(prop).confidence >= other.metadata(prop).confidence: continue self[prop] = other[prop] self._metadata[prop] = other.metadata(prop) def choose_int(g1, g2): """Function used by merge_similar_guesses to choose between 2 possible properties when they are integers.""" v1, c1 = g1 # value, confidence v2, c2 = g2 if v1 == v2: return v1, 1 - (1 - c1) * (1 - c2) else: if c1 >= c2: return v1, c1 - c2 / 2 else: return v2, c2 - c1 / 2 def choose_string(g1, g2): """Function used by merge_similar_guesses to choose between 2 possible properties when they are strings. If the 2 strings are similar or have common words longer than 3 letters, the one with highest confidence is returned with an increased confidence. If the 2 strings are dissimilar, the one with the higher confidence is returned, with a weaker confidence. Note that here, 'similar' means that 2 strings are either equal, or that they differ very little, such as one string being the other one with the 'the' word prepended to it. >>> s(choose_string(('Hello', 0.75), ('World', 0.5))) ('Hello', 0.5) >>> s(choose_string(('Hello', 0.5), ('hello', 0.5))) ('Hello', 0.75) >>> s(choose_string(('Hello', 0.4), ('Hello World', 0.4))) ('Hello', 0.64) >>> s(choose_string(('simpsons', 0.5), ('The Simpsons', 0.5))) ('The Simpsons', 0.75) """ v1, c1 = g1 # value, confidence v2, c2 = g2 if not v1: return g2 elif not v2: return g1 v1, v2 = v1.strip(), v2.strip() v1l, v2l = v1.lower(), v2.lower() combined_prob = 1 - (1 - c1) * (1 - c2) if v1l == v2l: return v1, combined_prob # check for common patterns elif v1l == 'the ' + v2l: return v1, combined_prob elif v2l == 'the ' + v1l: return v2, combined_prob # If the 2 strings have common words longer than 3 letters, # return the one with highest confidence. commons = common_words(v1l, v2l) for common_word in commons: if len(common_word) > 3: if c1 >= c2: return v1, combined_prob else: return v2, combined_prob # in case of conflict, return the one with highest confidence else: if c1 >= c2: return v1, c1 - c2 / 2 else: return v2, c2 - c1 / 2 def _merge_similar_guesses_nocheck(guesses, prop, choose): """Take a list of guesses and merge those which have the same properties, increasing or decreasing the confidence depending on whether their values are similar. This function assumes there are at least 2 valid guesses.""" similar = [guess for guess in guesses if prop in guess] g1, g2 = similar[0], similar[1] # merge only this prop of s2 into s1, updating the confidence for the # considered property v1, v2 = g1[prop], g2[prop] c1, c2 = g1.confidence(prop), g2.confidence(prop) new_value, new_confidence = choose((v1, c1), (v2, c2)) if new_confidence >= c1: msg = "Updating matching property '%s' with confidence %.2f" else: msg = "Updating non-matching property '%s' with confidence %.2f" log.debug(msg % (prop, new_confidence)) g1.set(prop, new_value, confidence=new_confidence) g2.pop(prop) # remove g2 if there are no properties left if not g2.keys(): guesses.remove(g2) def merge_similar_guesses(guesses, prop, choose): """Take a list of guesses and merge those which have the same properties, increasing or decreasing the confidence depending on whether their values are similar.""" similar = [guess for guess in guesses if prop in guess] if len(similar) < 2: # nothing to merge return if len(similar) == 2: _merge_similar_guesses_nocheck(guesses, prop, choose) if len(similar) > 2: log.debug('complex merge, trying our best...') before = len(guesses) _merge_similar_guesses_nocheck(guesses, prop, choose) after = len(guesses) if after < before: # recurse only when the previous call actually did something, # otherwise we end up in an infinite loop merge_similar_guesses(guesses, prop, choose) def merge_all(guesses, append=None): """Merge all the guesses in a single result, remove very unlikely values, and return it. You can specify a list of properties that should be appended into a list instead of being merged. >>> s(merge_all([ Guess({'season': 2}, confidence=0.6), ... Guess({'episodeNumber': 13}, confidence=0.8) ]) ... ) == {'season': 2, 'episodeNumber': 13} True >>> s(merge_all([ Guess({'episodeNumber': 27}, confidence=0.02), ... Guess({'season': 1}, confidence=0.2) ]) ... ) == {'season': 1} True >>> s(merge_all([ Guess({'other': 'PROPER'}, confidence=0.8), ... Guess({'releaseGroup': '2HD'}, confidence=0.8) ], ... append=['other']) ... ) == {'releaseGroup': '2HD', 'other': ['PROPER']} True """ result = Guess() if not guesses: return result if append is None: append = [] for g in guesses: # first append our appendable properties for prop in append: if prop in g: if isinstance(g[prop], (list, set)): new_values = result.get(prop, []) + list(g[prop]) else: new_values = result.get(prop, []) + [g[prop]] result.set(prop, new_values, # TODO: what to do with confidence here? maybe an # arithmetic mean... confidence=g.metadata(prop).confidence, input=g.metadata(prop).input, span=g.metadata(prop).span, prop=g.metadata(prop).prop) del g[prop] # then merge the remaining ones dups = set(result) & set(g) if dups: log.debug('duplicate properties %s in merged result...' % [(result[p], g[p]) for p in dups]) result.update_highest_confidence(g) # delete very unlikely values for p in list(result.keys()): if result.confidence(p) < 0.05: del result[p] # make sure our appendable properties contain unique values for prop in append: try: value = result[prop] if isinstance(value, list): result[prop] = list(set(value)) else: result[prop] = [value] except KeyError: pass return result def smart_merge(guesses): """First tries to merge well-known similar properties, and then merges the rest with a merge_all call. Should be the function to call in most cases, unless one wants to have more control. Warning: this function is destructive, ie: it will merge the list in-place. """ # 1- try to merge similar information together and give it a higher # confidence for int_part in ('year', 'season', 'episodeNumber'): merge_similar_guesses(guesses, int_part, choose_int) for string_part in ('title', 'series', 'container', 'format', 'releaseGroup', 'website', 'audioCodec', 'videoCodec', 'screenSize', 'episodeFormat', 'audioChannels', 'idNumber', 'container'): merge_similar_guesses(guesses, string_part, choose_string) # 2- merge the rest, potentially discarding information not properly # merged before result = merge_all(guesses, append=['language', 'subtitleLanguage', 'other', 'episodeDetails', 'unidentified']) return result guessit-0.11.0/guessit/language.py0000664000175000017500000002647012572377773017725 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import re import logging from guessit import u from guessit.textutils import find_words from babelfish import Language, Country import babelfish from guessit.guess import Guess __all__ = ['Language', 'UNDETERMINED', 'search_language', 'guess_language'] log = logging.getLogger(__name__) UNDETERMINED = babelfish.Language('und') SYN = {('und', None): ['unknown', 'inconnu', 'unk', 'un'], ('ell', None): ['gr', 'greek'], ('spa', None): ['esp', 'español'], ('fra', None): ['français', 'vf', 'vff', 'vfi'], ('swe', None): ['se'], ('por', 'BR'): ['po', 'pb', 'pob', 'br', 'brazilian'], ('cat', None): ['català'], ('ces', None): ['cz'], ('ukr', None): ['ua'], ('zho', None): ['cn'], ('jpn', None): ['jp'], ('hrv', None): ['scr'], ('mul', None): ['multi', 'dl'], # http://scenelingo.wordpress.com/2009/03/24/what-does-dl-mean/ } class GuessitConverter(babelfish.LanguageReverseConverter): _with_country_regexp = re.compile('(.*)\((.*)\)') _with_country_regexp2 = re.compile('(.*)-(.*)') def __init__(self): self.guessit_exceptions = {} for (alpha3, country), synlist in SYN.items(): for syn in synlist: self.guessit_exceptions[syn.lower()] = (alpha3, country, None) @property def codes(self): return (babelfish.language_converters['alpha3b'].codes | babelfish.language_converters['alpha2'].codes | babelfish.language_converters['name'].codes | babelfish.language_converters['opensubtitles'].codes | babelfish.country_converters['name'].codes | frozenset(self.guessit_exceptions.keys())) @staticmethod def convert(alpha3, country=None, script=None): return str(babelfish.Language(alpha3, country, script)) def reverse(self, name): with_country = (GuessitConverter._with_country_regexp.match(name) or GuessitConverter._with_country_regexp2.match(name)) name = u(name.lower()) if with_country: lang = Language.fromguessit(with_country.group(1).strip()) lang.country = babelfish.Country.fromguessit(with_country.group(2).strip()) return lang.alpha3, lang.country.alpha2 if lang.country else None, lang.script or None # exceptions come first, as they need to override a potential match # with any of the other guessers try: return self.guessit_exceptions[name] except KeyError: pass for conv in [babelfish.Language, babelfish.Language.fromalpha3b, babelfish.Language.fromalpha2, babelfish.Language.fromname, babelfish.Language.fromopensubtitles]: try: c = conv(name) return c.alpha3, c.country, c.script except (ValueError, babelfish.LanguageReverseError): pass raise babelfish.LanguageReverseError(name) babelfish.language_converters['guessit'] = GuessitConverter() COUNTRIES_SYN = {'ES': ['españa'], 'GB': ['UK'], 'BR': ['brazilian', 'bra'], # FIXME: this one is a bit of a stretch, not sure how to do # it properly, though... 'MX': ['Latinoamérica', 'latin america'] } class GuessitCountryConverter(babelfish.CountryReverseConverter): def __init__(self): self.guessit_exceptions = {} for alpha2, synlist in COUNTRIES_SYN.items(): for syn in synlist: self.guessit_exceptions[syn.lower()] = alpha2 @property def codes(self): return (babelfish.country_converters['name'].codes | frozenset(babelfish.COUNTRIES.values()) | frozenset(self.guessit_exceptions.keys())) @staticmethod def convert(alpha2): if alpha2 == 'GB': return 'UK' return str(Country(alpha2)) def reverse(self, name): # exceptions come first, as they need to override a potential match # with any of the other guessers try: return self.guessit_exceptions[name.lower()] except KeyError: pass try: return babelfish.Country(name.upper()).alpha2 except ValueError: pass for conv in [babelfish.Country.fromname]: try: return conv(name).alpha2 except babelfish.CountryReverseError: pass raise babelfish.CountryReverseError(name) babelfish.country_converters['guessit'] = GuessitCountryConverter() # list of common words which could be interpreted as languages, but which # are far too common to be able to say they represent a language in the # middle of a string (where they most likely carry their commmon meaning) LNG_COMMON_WORDS = frozenset([ # english words 'is', 'it', 'am', 'mad', 'men', 'man', 'run', 'sin', 'st', 'to', 'no', 'non', 'war', 'min', 'new', 'car', 'day', 'bad', 'bat', 'fan', 'fry', 'cop', 'zen', 'gay', 'fat', 'one', 'cherokee', 'got', 'an', 'as', 'cat', 'her', 'be', 'hat', 'sun', 'may', 'my', 'mr', 'rum', 'pi', 'bb', 'bt', 'tv', 'aw', 'by', 'md', 'mp', 'cd', 'lt', 'gt', 'in', 'ad', 'ice', 'ay', 'at', 'star', 'so', # french words 'bas', 'de', 'le', 'son', 'ne', 'ca', 'ce', 'et', 'que', 'mal', 'est', 'vol', 'or', 'mon', 'se', 'je', 'tu', 'me', 'ne', 'ma', 'va', 'au', # japanese words, 'wa', 'ga', 'ao', # spanish words 'la', 'el', 'del', 'por', 'mar', 'al', # other 'ind', 'arw', 'ts', 'ii', 'bin', 'chan', 'ss', 'san', 'oss', 'iii', 'vi', 'ben', 'da', 'lt', 'ch', 'sr', 'ps', 'cx', # new from babelfish 'mkv', 'avi', 'dmd', 'the', 'dis', 'cut', 'stv', 'des', 'dia', 'and', 'cab', 'sub', 'mia', 'rim', 'las', 'une', 'par', 'srt', 'ano', 'toy', 'job', 'gag', 'reel', 'www', 'for', 'ayu', 'csi', 'ren', 'moi', 'sur', 'fer', 'fun', 'two', 'big', 'psy', 'air', # movie title 'brazil', # release groups 'bs', # Bosnian 'kz', # countries 'gt', 'lt', 'im', # part/pt 'pt' ]) LNG_COMMON_WORDS_STRICT = frozenset(['brazil']) subtitle_prefixes = ['sub', 'subs', 'st', 'vost', 'subforced', 'fansub', 'hardsub'] subtitle_suffixes = ['subforced', 'fansub', 'hardsub', 'sub', 'subs'] lang_prefixes = ['true'] all_lang_prefixes_suffixes = subtitle_prefixes + subtitle_suffixes + lang_prefixes def find_possible_languages(string, allowed_languages=None): """Find possible languages in the string :return: list of tuple (property, Language, lang_word, word) """ common_words = None if allowed_languages: common_words = LNG_COMMON_WORDS_STRICT else: common_words = LNG_COMMON_WORDS words = find_words(string) valid_words = [] for word in words: lang_word = word.lower() key = 'language' for prefix in subtitle_prefixes: if lang_word.startswith(prefix): lang_word = lang_word[len(prefix):] key = 'subtitleLanguage' for suffix in subtitle_suffixes: if lang_word.endswith(suffix): lang_word = lang_word[:len(suffix)] key = 'subtitleLanguage' for prefix in lang_prefixes: if lang_word.startswith(prefix): lang_word = lang_word[len(prefix):] if lang_word not in common_words and word.lower() not in common_words: try: lang = Language.fromguessit(lang_word) if allowed_languages: if lang.name.lower() in allowed_languages or lang.alpha2.lower() in allowed_languages or lang.alpha3.lower() in allowed_languages: valid_words.append((key, lang, lang_word, word)) # Keep language with alpha2 equivalent. Others are probably # uncommon languages. elif lang == 'mul' or hasattr(lang, 'alpha2'): valid_words.append((key, lang, lang_word, word)) except babelfish.Error: pass return valid_words def search_language(string, allowed_languages=None): """Looks for language patterns, and if found return the language object, its group span and an associated confidence. you can specify a list of allowed languages using the lang_filter argument, as in lang_filter = [ 'fr', 'eng', 'spanish' ] >>> search_language('movie [en].avi')['language'] >>> search_language('the zen fat cat and the gay mad men got a new fan', allowed_languages = ['en', 'fr', 'es']) """ if allowed_languages: allowed_languages = set(Language.fromguessit(lang) for lang in allowed_languages) confidence = 1.0 # for all of them for prop, language, lang, word in find_possible_languages(string, allowed_languages): pos = string.find(word) end = pos + len(word) # only allow those languages that have a 2-letter code, those that # don't are too esoteric and probably false matches # if language.lang not in lng3_to_lng2: # continue # confidence depends on alpha2, alpha3, english name, ... if len(lang) == 2: confidence = 0.8 elif len(lang) == 3: confidence = 0.9 elif prop == 'subtitleLanguage': confidence = 0.6 # Subtitle prefix found with language else: # Note: we could either be really confident that we found a # language or assume that full language names are too # common words and lower their confidence accordingly confidence = 0.3 # going with the low-confidence route here return Guess({prop: language}, confidence=confidence, input=string, span=(pos, end)) return None def guess_language(text): # pragma: no cover """Guess the language in which a body of text is written. This uses the external guess-language python module, and will fail and return Language(Undetermined) if it is not installed. """ try: from guess_language import guessLanguage return Language.fromguessit(guessLanguage(text)) except ImportError: log.error('Cannot detect the language of the given text body, missing dependency: guess-language') log.error('Please install it from PyPI, by doing eg: pip install guess-language') return UNDETERMINED guessit-0.11.0/guessit/containers.py0000664000175000017500000007256112571103570020266 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # Copyright (c) 2013 Rémi Alvergnat # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import types from .patterns import compile_pattern, sep from . import base_text_type from .guess import Guess def _get_span(prop, match): """Retrieves span for a match""" if not prop.global_span and match.re.groups: start = None end = None for i in range(1, match.re.groups + 1): span = match.span(i) if start is None or span[0] < start: start = span[0] if end is None or span[1] > end: end = span[1] return start, end else: return match.span() def _trim_span(span, value, blanks = sep): start, end = span for i in range(0, len(value)): if value[i] in blanks: start += 1 else: break for i in reversed(range(0, len(value))): if value[i] in blanks: end -= 1 else: break if end <= start: return -1, -1 return start, end def _get_groups(compiled_re): """ Retrieves groups from re :return: list of group names """ if compiled_re.groups: indexgroup = {} for k, i in compiled_re.groupindex.items(): indexgroup[i] = k ret = [] for i in range(1, compiled_re.groups + 1): ret.append(indexgroup.get(i, i)) return ret else: return [None] class NoValidator(object): @staticmethod def validate(prop, string, node, match, entry_start, entry_end): return True class LeftValidator(object): """Make sure our match is starting by separator, or by another entry""" @staticmethod def validate(prop, string, node, match, entry_start, entry_end): span = _get_span(prop, match) span = _trim_span(span, string[span[0]:span[1]]) start, end = span sep_start = start <= 0 or string[start - 1] in sep start_by_other = start in entry_end if not sep_start and not start_by_other: return False return True class RightValidator(object): """Make sure our match is ended by separator, or by another entry""" @staticmethod def validate(prop, string, node, match, entry_start, entry_end): span = _get_span(prop, match) span = _trim_span(span, string[span[0]:span[1]]) start, end = span sep_end = end >= len(string) or string[end] in sep end_by_other = end in entry_start if not sep_end and not end_by_other: return False return True class ChainedValidator(object): def __init__(self, *validators): self._validators = validators def validate(self, prop, string, node, match, entry_start, entry_end): for validator in self._validators: if not validator.validate(prop, string, node, match, entry_start, entry_end): return False return True class SameKeyValidator(object): def __init__(self, validator_function): self.validator_function = validator_function def validate(self, prop, string, node, match, entry_start, entry_end): path_nodes = [path_node for path_node in node.ancestors if path_node.category == 'path'] if path_nodes: path_node = path_nodes[0] else: path_node = node.root for key in prop.keys: for same_value_leaf in path_node.leaves_containing(key): ret = self.validator_function(same_value_leaf, key, prop, string, node, match, entry_start, entry_end) if ret is not None: return ret return True class OnlyOneValidator(SameKeyValidator): """ Check that there's only one occurence of key for current directory """ def __init__(self): super(OnlyOneValidator, self).__init__(lambda same_value_leaf, key, prop, string, node, match, entry_start, entry_end: False) class DefaultValidator(object): """Make sure our match is surrounded by separators, or by another entry""" def validate(self, prop, string, node, match, entry_start, entry_end): span = _get_span(prop, match) span = _trim_span(span, string[span[0]:span[1]]) return DefaultValidator.validate_string(string, span, entry_start, entry_end) @staticmethod def validate_string(string, span, entry_start=None, entry_end=None): start, end = span sep_start = start <= 0 or string[start - 1] in sep sep_end = end >= len(string) or string[end] in sep start_by_other = start in entry_end if entry_end else False end_by_other = end in entry_start if entry_start else False if (sep_start or start_by_other) and (sep_end or end_by_other): return True return False class FunctionValidator(object): def __init__(self, function): self.function = function def validate(self, prop, string, node, match, entry_start, entry_end): return self.function(prop, string, node, match, entry_start, entry_end) class FormatterValidator(object): def __init__(self, group_name=None, formatted_validator=None): self.group_name = group_name self.formatted_validator = formatted_validator def validate(self, prop, string, node, match, entry_start, entry_end): if self.group_name: formatted = prop.format(match.group(self.group_name), self.group_name) else: formatted = prop.format(match.group()) if self.formatted_validator: return self.formatted_validator(formatted) else: return formatted def _get_positions(prop, string, node, match, entry_start, entry_end): span = match.span() start = span[0] end = span[1] at_start = True at_end = True while start > 0: start -= 1 if string[start] not in sep: at_start = False break while end < len(string) - 1: end += 1 if string[end] not in sep: at_end = False break return at_start, at_end class WeakValidator(DefaultValidator): """Make sure our match is surrounded by separators and is the first or last element in the string""" def validate(self, prop, string, node, match, entry_start, entry_end): if super(WeakValidator, self).validate(prop, string, node, match, entry_start, entry_end): at_start, at_end = _get_positions(prop, string, node, match, entry_start, entry_end) return at_start or at_end return False class NeighborValidator(DefaultValidator): """Make sure the node is next another one""" def validate(self, prop, string, node, match, entry_start, entry_end): at_start, at_end = _get_positions(prop, string, node, match, entry_start, entry_end) if at_start: previous_leaf = node.root.previous_leaf(node) if previous_leaf is not None: return True if at_end: next_leaf = node.root.next_leaf(node) if next_leaf is not None: return True return False class FullMatchValidator(DefaultValidator): """Make sure the node match fully""" def validate(self, prop, string, node, match, entry_start, entry_end): at_start, at_end = _get_positions(prop, string, node, match, entry_start, entry_end) return at_start and at_end class LeavesValidator(DefaultValidator): def __init__(self, lambdas=None, previous_lambdas=None, next_lambdas=None, both_side=False, default_=True): self.previous_lambdas = previous_lambdas if previous_lambdas is not None else [] self.next_lambdas = next_lambdas if next_lambdas is not None else [] if lambdas: self.previous_lambdas.extend(lambdas) self.next_lambdas.extend(lambdas) self.both_side = both_side self.default_ = default_ """Make sure our match is surrounded by separators and validates defined lambdas""" def validate(self, prop, string, node, match, entry_start, entry_end): if self.default_: super_ret = super(LeavesValidator, self).validate(prop, string, node, match, entry_start, entry_end) else: super_ret = True if not super_ret: return False previous_ = self._validate_previous(prop, string, node, match, entry_start, entry_end) next_ = self._validate_next(prop, string, node, match, entry_start, entry_end) if previous_ is None and next_ is None: return super_ret if self.both_side: return previous_ and next_ else: return previous_ or next_ def _validate_previous(self, prop, string, node, match, entry_start, entry_end): if self.previous_lambdas: for leaf in node.root.previous_leaves(node): for lambda_ in self.previous_lambdas: ret = self._check_rule(lambda_, leaf) if ret is not None: return ret return False def _validate_next(self, prop, string, node, match, entry_start, entry_end): if self.next_lambdas: for leaf in node.root.next_leaves(node): for lambda_ in self.next_lambdas: ret = self._check_rule(lambda_, leaf) if ret is not None: return ret return False @staticmethod def _check_rule(lambda_, previous_leaf): return lambda_(previous_leaf) class _Property: """Represents a property configuration.""" def __init__(self, keys=None, pattern=None, canonical_form=None, canonical_from_pattern=True, confidence=1.0, enhance=True, global_span=False, validator=DefaultValidator(), formatter=None, disabler=None, confidence_lambda=None, remove_duplicates=False): """ :param keys: Keys of the property (format, screenSize, ...) :type keys: string :param canonical_form: Unique value of the property (DVD, 720p, ...) :type canonical_form: string :param pattern: Regexp pattern :type pattern: string :param confidence: confidence :type confidence: float :param enhance: enhance the pattern :type enhance: boolean :param global_span: if True, the whole match span will used to create the Guess. Else, the span from the capturing groups will be used. :type global_span: boolean :param validator: Validator to use :type validator: :class:`DefaultValidator` :param formatter: Formater to use :type formatter: function :param remove_duplicates: Keep only the last match if multiple values are found :type remove_duplicates: bool """ if isinstance(keys, list): self.keys = keys elif isinstance(keys, base_text_type): self.keys = [keys] else: self.keys = [] self.canonical_form = canonical_form if pattern is not None: self.pattern = pattern else: self.pattern = canonical_form if self.canonical_form is None and canonical_from_pattern: self.canonical_form = self.pattern self.compiled = compile_pattern(self.pattern, enhance=enhance) for group_name in _get_groups(self.compiled): if isinstance(group_name, base_text_type) and not group_name in self.keys: self.keys.append(group_name) if not self.keys: raise ValueError("No property key is defined") self.confidence = confidence self.confidence_lambda = confidence_lambda self.global_span = global_span self.validator = validator self.formatter = formatter self.disabler = disabler self.remove_duplicates = remove_duplicates def disabled(self, options): if self.disabler: return self.disabler(options) return False def format(self, value, group_name=None): """Retrieves the final value from re group match value""" formatter = None if isinstance(self.formatter, dict): formatter = self.formatter.get(group_name) if formatter is None and group_name is not None: formatter = self.formatter.get(None) else: formatter = self.formatter if isinstance(formatter, types.FunctionType): return formatter(value) elif formatter is not None: return formatter.format(value) return value def __repr__(self): return "%s: %s" % (self.keys, self.canonical_form if self.canonical_form else self.pattern) class PropertiesContainer(object): def __init__(self, **kwargs): self._properties = [] self.default_property_kwargs = kwargs def unregister_property(self, name, *canonical_forms): """Unregister a property canonical forms If canonical_forms are specified, only those values will be unregistered :param name: Property name to unregister :type name: string :param canonical_forms: Values to unregister :type canonical_forms: varargs of string """ _properties = [prop for prop in self._properties if prop.name == name and (not canonical_forms or prop.canonical_form in canonical_forms)] def register_property(self, name, *patterns, **property_params): """Register property with defined canonical form and patterns. :param name: name of the property (format, screenSize, ...) :type name: string :param patterns: regular expression patterns to register for the property canonical_form :type patterns: varargs of string """ properties = [] for pattern in patterns: params = dict(self.default_property_kwargs) params.update(property_params) if isinstance(pattern, dict): params.update(pattern) prop = _Property(name, **params) else: prop = _Property(name, pattern, **params) self._properties.append(prop) properties.append(prop) return properties def register_canonical_properties(self, name, *canonical_forms, **property_params): """Register properties from their canonical forms. :param name: name of the property (releaseGroup, ...) :type name: string :param canonical_forms: values of the property ('ESiR', 'WAF', 'SEPTiC', ...) :type canonical_forms: varargs of strings """ properties = [] for canonical_form in canonical_forms: params = dict(property_params) params['canonical_form'] = canonical_form properties.extend(self.register_property(name, canonical_form, **property_params)) return properties def unregister_all_properties(self): """Unregister all defined properties""" self._properties.clear() def find_properties(self, string, node, options, name=None, validate=True, re_match=False, sort=True, multiple=False): """Find all distinct properties for given string If no capturing group is defined in the property, value will be grabbed from the entire match. If one ore more unnamed capturing group is defined in the property, first capturing group will be used. If named capturing group are defined in the property, they will be returned as property key. If validate, found properties will be validated by their defined validator If re_match, re.match will be used instead of re.search. if sort, found properties will be sorted from longer match to shorter match. If multiple is False and multiple values are found for the same property, the more confident one will be returned. If multiple is False and multiple values are found for the same property and the same confidence, the longer will be returned. :param string: input string :type string: string :param node: current node of the matching tree :type node: :class:`guessit.matchtree.MatchTree` :param name: name of property to find :type name: string :param re_match: use re.match instead of re.search :type re_match: bool :param multiple: Allows multiple property values to be returned :type multiple: bool :return: found properties :rtype: list of tuples (:class:`_Property`, match, list of tuples (property_name, tuple(value_start, value_end))) :see: `_Property` :see: `register_property` :see: `register_canonical_properties` """ entry_start = {} entry_end = {} entries = [] duplicate_matches = {} ret = [] if not string.strip(): return ret # search all properties for prop in self.get_properties(name): if not prop.disabled(options): valid_match = None if re_match: match = prop.compiled.match(string) if match: entries.append((prop, match)) else: matches = list(prop.compiled.finditer(string)) if prop.remove_duplicates: duplicate_matches[prop] = matches for match in matches: entries.append((prop, match)) for prop, match in entries: # compute confidence if prop.confidence_lambda: computed_confidence = prop.confidence_lambda(match) if computed_confidence is not None: prop.confidence = computed_confidence entries.sort(key=lambda entry: -entry[0].confidence) # sort entries, from most confident to less confident if validate: # compute entries start and ends for prop, match in entries: start, end = _get_span(prop, match) if start not in entry_start: entry_start[start] = [prop] else: entry_start[start].append(prop) if end not in entry_end: entry_end[end] = [prop] else: entry_end[end].append(prop) # remove invalid values while True: invalid_entries = [] for entry in entries: prop, match = entry if not prop.validator.validate(prop, string, node, match, entry_start, entry_end): invalid_entries.append(entry) if not invalid_entries: break for entry in invalid_entries: prop, match = entry entries.remove(entry) prop_duplicate_matches = duplicate_matches.get(prop) if prop_duplicate_matches: prop_duplicate_matches.remove(match) invalid_span = _get_span(prop, match) start = invalid_span[0] end = invalid_span[1] entry_start[start].remove(prop) if not entry_start.get(start): del entry_start[start] entry_end[end].remove(prop) if not entry_end.get(end): del entry_end[end] for prop, prop_duplicate_matches in duplicate_matches.items(): # Keeping the last valid match only. # Needed for the.100.109.hdtv-lol.mp4 for duplicate_match in prop_duplicate_matches[:-1]: entries.remove((prop, duplicate_match)) if multiple: ret = entries else: # keep only best match if multiple values where found entries_dict = {} for entry in entries: for key in prop.keys: if key not in entries_dict: entries_dict[key] = [] entries_dict[key].append(entry) for key_entries in entries_dict.values(): if multiple: for entry in key_entries: ret.append(entry) else: best_ret = {} best_prop, best_match = None, None if len(key_entries) == 1: best_prop, best_match = key_entries[0] else: for prop, match in key_entries: start, end = _get_span(prop, match) if not best_prop or \ best_prop.confidence < prop.confidence or \ best_prop.confidence == prop.confidence and \ best_match.span()[1] - best_match.span()[0] < match.span()[1] - match.span()[0]: best_prop, best_match = prop, match best_ret[best_prop] = best_match for prop, match in best_ret.items(): ret.append((prop, match)) if sort: def _sorting(x): _, x_match = x x_start, x_end = x_match.span() return x_start - x_end ret.sort(key=_sorting) return ret def as_guess(self, found_properties, input=None, filter_=None, sep_replacement=None, multiple=False, *args, **kwargs): if filter_ is None: filter_ = lambda property, *args, **kwargs: True guesses = [] if multiple else None for prop, match in found_properties: first_key = None for key in prop.keys: # First property key will be used as base for effective name if isinstance(key, base_text_type): if first_key is None: first_key = key break property_name = first_key if first_key else None span = _get_span(prop, match) guess = Guess(confidence=prop.confidence, input=input, span=span, prop=property_name) groups = _get_groups(match.re) for group_name in groups: name = group_name if isinstance(group_name, base_text_type) else property_name if property_name not in groups else None if name: value = self._effective_prop_value(prop, group_name, input, match.span(group_name) if group_name else match.span(), sep_replacement) if not value is None: is_string = isinstance(value, base_text_type) if not is_string or is_string and value: # Keep non empty strings and other defined objects if isinstance(value, dict): for k, v in value.items(): if k is None: k = name guess[k] = v else: if name in guess: if not isinstance(guess[name], list): guess[name] = [guess[name]] guess[name].append(value) else: guess[name] = value if group_name: guess.metadata(prop).span = match.span(group_name) if filter_(guess): if multiple: guesses.append(guess) else: return guess return guesses @staticmethod def _effective_prop_value(prop, group_name, input=None, span=None, sep_replacement=None): if prop.canonical_form: return prop.canonical_form if input is None: return None value = input if span is not None: value = value[span[0]:span[1]] value = input[span[0]:span[1]] if input else None if sep_replacement: for sep_char in sep: value = value.replace(sep_char, sep_replacement) if value: value = prop.format(value, group_name) return value def get_properties(self, name=None, canonical_form=None): """Retrieve properties :return: Properties :rtype: generator """ for prop in self._properties: if (name is None or name in prop.keys) and (canonical_form is None or prop.canonical_form == canonical_form): yield prop def get_supported_properties(self): supported_properties = {} for prop in self.get_properties(): for k in prop.keys: values = supported_properties.get(k) if not values: values = set() supported_properties[k] = values if prop.canonical_form: values.add(prop.canonical_form) return supported_properties class QualitiesContainer(): def __init__(self): self._qualities = {} def register_quality(self, name, canonical_form, rating): """Register a quality rating. :param name: Name of the property :type name: string :param canonical_form: Value of the property :type canonical_form: string :param rating: Estimated quality rating for the property :type rating: int """ property_qualities = self._qualities.get(name) if property_qualities is None: property_qualities = {} self._qualities[name] = property_qualities property_qualities[canonical_form] = rating def unregister_quality(self, name, *canonical_forms): """Unregister quality ratings for given property name. If canonical_forms are specified, only those values will be unregistered :param name: Name of the property :type name: string :param canonical_forms: Value of the property :type canonical_forms: string """ if not canonical_forms: if name in self._qualities: del self._qualities[name] else: property_qualities = self._qualities.get(name) if property_qualities is not None: for property_canonical_form in canonical_forms: if property_canonical_form in property_qualities: del property_qualities[property_canonical_form] if not property_qualities: del self._qualities[name] def clear_qualities(self,): """Unregister all defined quality ratings. """ self._qualities.clear() def rate_quality(self, guess, *props): """Rate the quality of guess. :param guess: Guess to rate :type guess: :class:`guessit.guess.Guess` :param props: Properties to include in the rating. if empty, rating will be performed for all guess properties. :type props: varargs of string :return: Quality of the guess. The higher, the better. :rtype: int """ rate = 0 if not props: props = guess.keys() for prop in props: prop_value = guess.get(prop) prop_qualities = self._qualities.get(prop) if prop_value is not None and prop_qualities is not None: rate += prop_qualities.get(prop_value, 0) return rate def best_quality_properties(self, props, *guesses): """Retrieve the best quality guess, based on given properties :param props: Properties to include in the rating :type props: list of strings :param guesses: Guesses to rate :type guesses: :class:`guessit.guess.Guess` :return: Best quality guess from all passed guesses :rtype: :class:`guessit.guess.Guess` """ best_guess = None best_rate = None for guess in guesses: rate = self.rate_quality(guess, *props) if best_rate is None or best_rate < rate: best_rate = rate best_guess = guess return best_guess def best_quality(self, *guesses): """Retrieve the best quality guess. :param guesses: Guesses to rate :type guesses: :class:`guessit.guess.Guess` :return: Best quality guess from all passed guesses :rtype: :class:`guessit.guess.Guess` """ best_guess = None best_rate = None for guess in guesses: rate = self.rate_quality(guess) if best_rate is None or best_rate < rate: best_rate = rate best_guess = guess return best_guess guessit-0.11.0/guessit/__main__.py0000664000175000017500000002777412571103570017647 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # Copyright (c) 2013 Rémi Alvergnat # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from collections import defaultdict import logging import os from guessit import PY2, u, guess_file_info from guessit.options import get_opts from guessit.__version__ import __version__ def guess_file(filename, info='filename', options=None, **kwargs): options = options or {} filename = u(filename) if not options.get('yaml') and not options.get('show_property'): print('For:', filename) guess = guess_file_info(filename, info, options, **kwargs) if not options.get('unidentified'): try: del guess['unidentified'] except KeyError: pass if options.get('show_property'): print(guess.get(options.get('show_property'), '')) return if options.get('yaml'): import yaml for k, v in guess.items(): if isinstance(v, list) and len(v) == 1: guess[k] = v[0] ystr = yaml.safe_dump({filename: dict(guess)}, default_flow_style=False, allow_unicode=True) i = 0 for yline in ystr.splitlines(): if i == 0: print("? " + yline[:-1]) elif i == 1: print(":" + yline[1:]) else: print(yline) i += 1 return print('GuessIt found:', guess.nice_string(options.get('advanced'))) def _supported_properties(): all_properties = defaultdict(list) transformers_properties = [] from guessit.plugins import transformers for transformer in transformers.all_transformers(): supported_properties = transformer.supported_properties() transformers_properties.append((transformer, supported_properties)) if isinstance(supported_properties, dict): for property_name, possible_values in supported_properties.items(): all_properties[property_name].extend(possible_values) else: for property_name in supported_properties: all_properties[property_name] # just make sure it exists return all_properties, transformers_properties def display_transformers(): print('GuessIt transformers:') _, transformers_properties = _supported_properties() for transformer, _ in transformers_properties: print('[@] %s (%s)' % (transformer.name, transformer.priority)) def display_properties(options): values = options.values transformers = options.transformers name_only = options.name_only print('GuessIt properties:') all_properties, transformers_properties = _supported_properties() if name_only: # the 'container' property does not apply when using the --name-only # option del all_properties['container'] if transformers: for transformer, properties_list in transformers_properties: print('[@] %s (%s)' % (transformer.name, transformer.priority)) for property_name in properties_list: property_values = all_properties.get(property_name) print(' [+] %s' % (property_name,)) if property_values and values: _display_property_values(property_name, indent=4) else: properties_list = sorted(all_properties.keys()) for property_name in properties_list: property_values = all_properties.get(property_name) print(' [+] %s' % (property_name,)) if property_values and values: _display_property_values(property_name, indent=4) def _display_property_values(property_name, indent=2): all_properties, _ = _supported_properties() property_values = all_properties.get(property_name) for property_value in property_values: print(indent * ' ' + '[!] %s' % (property_value,)) def run_demo(episodes=True, movies=True, options=None): # NOTE: tests should not be added here but rather in the tests/ folder # this is just intended as a quick example if episodes: testeps = ['Series/Californication/Season 2/Californication.2x05.Vaginatown.HDTV.XviD-0TV.[tvu.org.ru].avi', 'Series/dexter/Dexter.5x02.Hello,.Bandit.ENG.-.sub.FR.HDTV.XviD-AlFleNi-TeaM.[tvu.org.ru].avi', 'Series/Treme/Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.[tvu.org.ru].avi', 'Series/Duckman/Duckman - 101 (01) - 20021107 - I, Duckman.avi', 'Series/Duckman/Duckman - S1E13 Joking The Chicken (unedited).avi', 'Series/Simpsons/The_simpsons_s13e18_-_i_am_furious_yellow.mpg', 'Series/Simpsons/Saison 12 Français/Simpsons,.The.12x08.A.Bas.Le.Sergent.Skinner.FR.[tvu.org.ru].avi', 'Series/Dr._Slump_-_002_DVB-Rip_Catalan_by_kelf.avi', 'Series/Kaamelott/Kaamelott - Livre V - Second Volet - HD 704x396 Xvid 2 pass - Son 5.1 - TntRip by Slurm.avi'] for f in testeps: print('-' * 80) guess_file(f, options=options, type='episode') if movies: testmovies = ['Movies/Fear and Loathing in Las Vegas (1998)/Fear.and.Loathing.in.Las.Vegas.720p.HDDVD.DTS.x264-ESiR.mkv', 'Movies/El Dia de la Bestia (1995)/El.dia.de.la.bestia.DVDrip.Spanish.DivX.by.Artik[SEDG].avi', 'Movies/Blade Runner (1982)/Blade.Runner.(1982).(Director\'s.Cut).CD1.DVDRip.XviD.AC3-WAF.avi', 'Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv', 'Movies/Sin City (BluRay) (2005)/Sin.City.2005.BDRip.720p.x264.AC3-SEPTiC.mkv', 'Movies/Borat (2006)/Borat.(2006).R5.PROPER.REPACK.DVDRip.XviD-PUKKA.avi', '[XCT].Le.Prestige.(The.Prestige).DVDRip.[x264.HP.He-Aac.{Fr-Eng}.St{Fr-Eng}.Chaps].mkv', 'Battle Royale (2000)/Battle.Royale.(Batoru.Rowaiaru).(2000).(Special.Edition).CD1of2.DVDRiP.XviD-[ZeaL].avi', 'Movies/Brazil (1985)/Brazil_Criterion_Edition_(1985).CD2.English.srt', 'Movies/Persepolis (2007)/[XCT] Persepolis [H264+Aac-128(Fr-Eng)+ST(Fr-Eng)+Ind].mkv', 'Movies/Toy Story (1995)/Toy Story [HDTV 720p English-Spanish].mkv', 'Movies/Pirates of the Caribbean: The Curse of the Black Pearl (2003)/Pirates.Of.The.Carribean.DC.2003.iNT.DVDRip.XviD.AC3-NDRT.CD1.avi', 'Movies/Office Space (1999)/Office.Space.[Dual-DVDRip].[Spanish-English].[XviD-AC3-AC3].[by.Oswald].avi', 'Movies/The NeverEnding Story (1984)/The.NeverEnding.Story.1.1984.DVDRip.AC3.Xvid-Monteque.avi', 'Movies/Juno (2007)/Juno KLAXXON.avi', 'Movies/Chat noir, chat blanc (1998)/Chat noir, Chat blanc - Emir Kusturica (VO - VF - sub FR - Chapters).mkv', 'Movies/Wild Zero (2000)/Wild.Zero.DVDivX-EPiC.srt', 'Movies/El Bosque Animado (1987)/El.Bosque.Animado.[Jose.Luis.Cuerda.1987].[Xvid-Dvdrip-720x432].avi', 'testsmewt_bugs/movies/Baraka_Edition_Collector.avi' ] for f in testmovies: print('-' * 80) guess_file(f, options=options, type='movie') def submit_bug(filename, options): import requests # only import when needed from requests.exceptions import RequestException try: opts = dict((k, v) for k, v in options.__dict__.items() if v and k != 'submit_bug') r = requests.post('http://guessit.io/bugs', {'filename': filename, 'version': __version__, 'options': str(opts)}) if r.status_code == 200: print('Successfully submitted file: %s' % r.text) else: print('Could not submit bug at the moment, please try again later: %s %s' % (r.status_code, r.reason)) except RequestException as e: print('Could not submit bug at the moment, please try again later: %s' % e) def main(args=None, setup_logging=True): if setup_logging: from guessit import slogging slogging.setup_logging() if PY2: # pragma: no cover import codecs import locale import sys # see http://bugs.python.org/issue2128 if os.name == 'nt': for i, a in enumerate(sys.argv): sys.argv[i] = a.decode(locale.getpreferredencoding()) # see https://github.com/wackou/guessit/issues/43 # and http://stackoverflow.com/questions/4545661/unicodedecodeerror-when-redirecting-to-file # Wrap sys.stdout into a StreamWriter to allow writing unicode. sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout) # Needed for guessit.plugins.transformers.reload() to be called. from guessit.plugins import transformers if args: options = get_opts().parse_args(args) else: # pragma: no cover options = get_opts().parse_args() if options.verbose: logging.getLogger().setLevel(logging.DEBUG) help_required = True if options.properties or options.values: display_properties(options) help_required = False elif options.transformers: display_transformers() help_required = False if options.demo: run_demo(episodes=True, movies=True, options=vars(options)) help_required = False if options.version: print('+-------------------------------------------------------+') print('+ GuessIt ' + __version__ + (28-len(__version__)) * ' ' + '+') print('+-------------------------------------------------------+') print('| Please report any bug or feature request at |') print('| https://github.com/wackou/guessit/issues. |') print('+-------------------------------------------------------+') help_required = False if options.yaml: try: import yaml, babelfish def default_representer(dumper, data): return dumper.represent_str(str(data)) yaml.SafeDumper.add_representer(babelfish.Language, default_representer) yaml.SafeDumper.add_representer(babelfish.Country, default_representer) except ImportError: # pragma: no cover print('PyYAML not found. Using default output.') filenames = [] if options.filename: filenames.extend(options.filename) if options.input_file: input_file = open(options.input_file, 'r') try: filenames.extend([line.strip() for line in input_file.readlines()]) finally: input_file.close() filenames = filter(lambda f: f, filenames) if filenames: if options.submit_bug: for filename in filenames: help_required = False submit_bug(filename, options) else: for filename in filenames: help_required = False guess_file(filename, info=options.info.split(','), options=vars(options)) if help_required: # pragma: no cover get_opts().print_help() if __name__ == '__main__': main() guessit-0.11.0/guessit/hash_ed2k.py0000664000175000017500000000420512534564763017755 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import hashlib import os.path from functools import reduce from guessit import s, to_hex def hash_file(filename): """Returns the ed2k hash of a given file. >>> testfile = os.path.join(os.path.dirname(__file__), 'test/dummy.srt') >>> s(hash_file(testfile)) 'ed2k://|file|dummy.srt|59|41F58B913AB3973F593BEBA8B8DF6510|/' """ return 'ed2k://|file|%s|%d|%s|/' % (os.path.basename(filename), os.path.getsize(filename), hash_filehash(filename).upper()) def hash_filehash(filename): """Returns the ed2k hash of a given file. This function is taken from: http://www.radicand.org/blog/orz/2010/2/21/edonkey2000-hash-in-python/ """ md4 = hashlib.new('md4').copy def gen(f): while True: x = f.read(9728000) if x: yield x else: return def md4_hash(data): m = md4() m.update(data) return m with open(filename, 'rb') as f: a = gen(f) hashes = [md4_hash(data).digest() for data in a] if len(hashes) == 1: return to_hex(hashes[0]) else: return md4_hash(reduce(lambda a, d: a + d, hashes, "")).hexd guessit-0.11.0/guessit/tlds-alpha-by-domain.txt0000664000175000017500000000322612410027040022173 0ustar toilaltoilal00000000000000# Version 2013112900, Last Updated Fri Nov 29 07:07:01 2013 UTC AC AD AE AERO AF AG AI AL AM AN AO AQ AR ARPA AS ASIA AT AU AW AX AZ BA BB BD BE BF BG BH BI BIKE BIZ BJ BM BN BO BR BS BT BV BW BY BZ CA CAMERA CAT CC CD CF CG CH CI CK CL CLOTHING CM CN CO COM CONSTRUCTION CONTRACTORS COOP CR CU CV CW CX CY CZ DE DIAMONDS DIRECTORY DJ DK DM DO DZ EC EDU EE EG ENTERPRISES EQUIPMENT ER ES ESTATE ET EU FI FJ FK FM FO FR GA GALLERY GB GD GE GF GG GH GI GL GM GN GOV GP GQ GR GRAPHICS GS GT GU GURU GW GY HK HM HN HOLDINGS HR HT HU ID IE IL IM IN INFO INT IO IQ IR IS IT JE JM JO JOBS JP KE KG KH KI KITCHEN KM KN KP KR KW KY KZ LA LAND LB LC LI LIGHTING LK LR LS LT LU LV LY MA MC MD ME MG MH MIL MK ML MM MN MO MOBI MP MQ MR MS MT MU MUSEUM MV MW MX MY MZ NA NAME NC NE NET NF NG NI NL NO NP NR NU NZ OM ORG PA PE PF PG PH PHOTOGRAPHY PK PL PLUMBING PM PN POST PR PRO PS PT PW PY QA RE RO RS RU RW SA SB SC SD SE SEXY SG SH SI SINGLES SJ SK SL SM SN SO SR ST SU SV SX SY SZ TATTOO TC TD TECHNOLOGY TEL TF TG TH TIPS TJ TK TL TM TN TO TODAY TP TR TRAVEL TT TV TW TZ UA UG UK US UY UZ VA VC VE VENTURES VG VI VN VOYAGE VU WF WS XN--3E0B707E XN--45BRJ9C XN--80AO21A XN--80ASEHDB XN--80ASWG XN--90A3AC XN--CLCHC0EA0B2G2A9GCD XN--FIQS8S XN--FIQZ9S XN--FPCRJ9C3D XN--FZC2C9E2C XN--GECRJ9C XN--H2BRJ9C XN--J1AMH XN--J6W193G XN--KPRW13D XN--KPRY57D XN--L1ACC XN--LGBBAT1AD8J XN--MGB9AWBF XN--MGBA3A4F16A XN--MGBAAM7A8H XN--MGBAYH7GPA XN--MGBBH1A71E XN--MGBC0A9AZCG XN--MGBERP4A5D4AR XN--MGBX4CD0AB XN--NGBC5AZD XN--O3CW4H XN--OGBPF8FL XN--P1AI XN--PGBS0DH XN--Q9JYB4C XN--S9BRJ9C XN--UNUP4Y XN--WGBH1C XN--WGBL6A XN--XKC2AL3HYE2A XN--XKC2DL3A5EE0H XN--YFRO4I67O XN--YGBI2AMMX XXX YE YT ZA ZM ZW guessit-0.11.0/guessit/__init__.py0000664000175000017500000003116212571103570017650 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from .__version__ import __version__ __all__ = ['Guess', 'Language', 'guess_file_info', 'guess_video_info', 'guess_movie_info', 'guess_episode_info', 'default_options'] # Do python3 detection before importing any other module, to be sure that # it will then always be available # with code from http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python/ import sys if sys.version_info[0] >= 3: # pragma: no cover PY2, PY3 = False, True unicode_text_type = str native_text_type = str base_text_type = str def u(x): return str(x) def s(x): return x class UnicodeMixin(object): __str__ = lambda x: x.__unicode__() import binascii def to_hex(x): return binascii.hexlify(x).decode('utf-8') else: # pragma: no cover PY2, PY3 = True, False __all__ = [str(s) for s in __all__] # fix imports for python2 unicode_text_type = unicode native_text_type = str base_text_type = basestring def u(x): if isinstance(x, str): return x.decode('utf-8') if isinstance(x, list): return [u(s) for s in x] return unicode(x) def s(x): if isinstance(x, unicode): return x.encode('utf-8') if isinstance(x, list): return [s(y) for y in x] if isinstance(x, tuple): return tuple(s(y) for y in x) if isinstance(x, dict): return dict((s(key), s(value)) for key, value in x.items()) return x class UnicodeMixin(object): __str__ = lambda x: unicode(x).encode('utf-8') def to_hex(x): return x.encode('hex') range = xrange from guessit.guess import Guess, smart_merge from guessit.language import Language from guessit.matcher import IterativeMatcher from guessit.textutils import clean_default, is_camel, from_camel from copy import deepcopy import babelfish import os.path import logging from guessit.options import get_opts import shlex # Needed for guessit.plugins.transformers.reload() to be called. from guessit.plugins import transformers log = logging.getLogger(__name__) class NullHandler(logging.Handler): def emit(self, record): pass # let's be a nicely behaving library h = NullHandler() log.addHandler(h) def _guess_filename(filename, options=None, **kwargs): mtree = _build_filename_mtree(filename, options=options, **kwargs) if options.get('split_camel'): _add_camel_properties(mtree, options=options) return mtree.matched() def _build_filename_mtree(filename, options=None, **kwargs): mtree = IterativeMatcher(filename, options=options, **kwargs) second_pass_options = mtree.second_pass_options if second_pass_options: log.debug('Running 2nd pass with options: %s' % second_pass_options) merged_options = dict(options) merged_options.update(second_pass_options) mtree = IterativeMatcher(filename, options=merged_options, **kwargs) return mtree def _add_camel_properties(mtree, options=None, **kwargs): prop = 'title' if mtree.matched().get('type') != 'episode' else 'series' value = mtree.matched().get(prop) _guess_camel_string(mtree, value, options=options, skip_title=False, **kwargs) for leaf in mtree.match_tree.unidentified_leaves(): value = leaf.value _guess_camel_string(mtree, value, options=options, skip_title=True, **kwargs) def _guess_camel_string(mtree, string, options=None, skip_title=False, **kwargs): if string and is_camel(string): log.debug('"%s" is camel cased. Try to detect more properties.' % (string,)) uncameled_value = from_camel(string) merged_options = dict(options) if 'type' in mtree.match_tree.info: current_type = mtree.match_tree.info.get('type') if current_type and current_type != 'unknown': merged_options['type'] = current_type camel_tree = _build_filename_mtree(uncameled_value, options=merged_options, name_only=True, skip_title=skip_title, **kwargs) if len(camel_tree.matched()) > 0: mtree.matched().update(camel_tree.matched()) return True return False def guess_video_metadata(filename): """Gets the video metadata properties out of a given file. The file needs to exist on the filesystem to be able to be analyzed. An empty guess is returned otherwise. You need to have the Enzyme python package installed for this to work.""" result = Guess() def found(prop, value): result[prop] = value log.debug('Found with enzyme %s: %s' % (prop, value)) # first get the size of the file, in bytes try: size = os.stat(filename).st_size found('fileSize', size) except Exception as e: log.error('Cannot get video file size: %s' % e) # file probably does not exist, we might as well return now return result # then get additional metadata from the file using enzyme, if available try: import enzyme with open(filename) as f: mkv = enzyme.MKV(f) found('duration', mkv.info.duration.total_seconds()) if mkv.video_tracks: video_track = mkv.video_tracks[0] # resolution if video_track.height in (480, 720, 1080): if video_track.interlaced: found('screenSize', '%di' % video_track.height) else: found('screenSize', '%dp' % video_track.height) else: # TODO: do we want this? #found('screenSize', '%dx%d' % (video_track.width, video_track.height)) pass # video codec if video_track.codec_id == 'V_MPEG4/ISO/AVC': found('videoCodec', 'h264') elif video_track.codec_id == 'V_MPEG4/ISO/SP': found('videoCodec', 'DivX') elif video_track.codec_id == 'V_MPEG4/ISO/ASP': found('videoCodec', 'XviD') else: log.warning('MKV has no video track') if mkv.audio_tracks: audio_track = mkv.audio_tracks[0] # audio codec if audio_track.codec_id == 'A_AC3': found('audioCodec', 'AC3') elif audio_track.codec_id == 'A_DTS': found('audioCodec', 'DTS') elif audio_track.codec_id == 'A_AAC': found('audioCodec', 'AAC') else: log.warning('MKV has no audio track') if mkv.subtitle_tracks: embedded_subtitle_languages = set() for st in mkv.subtitle_tracks: try: if st.language: lang = babelfish.Language.fromalpha3b(st.language) elif st.name: lang = babelfish.Language.fromname(st.name) else: lang = babelfish.Language('und') except babelfish.Error: lang = babelfish.Language('und') embedded_subtitle_languages.add(lang) found('subtitleLanguage', embedded_subtitle_languages) else: log.debug('MKV has no subtitle track') return result except ImportError: log.error('Cannot get video file metadata, missing dependency: enzyme') log.error('Please install it from PyPI, by doing eg: pip install enzyme') return result except IOError as e: log.error('Could not open file: %s' % filename) log.error('Make sure it exists and is available for reading on the filesystem') log.error('Error: %s' % e) return result except enzyme.Error as e: log.error('Cannot guess video file metadata') log.error('enzyme.Error while reading file: %s' % filename) log.error('Error: %s' % e) return result default_options = {} def guess_file_info(filename, info=None, options=None, **kwargs): """info can contain the names of the various plugins, such as 'filename' to detect filename info, or 'hash_md5' to get the md5 hash of the file. >>> testfile = os.path.join(os.path.dirname(__file__), 'test/dummy.srt') >>> g = guess_file_info(testfile, info = ['hash_md5', 'hash_sha1']) >>> g['hash_md5'], g['hash_sha1'] ('64de6b5893cac24456c46a935ef9c359', 'a703fc0fa4518080505809bf562c6fc6f7b3c98c') """ info = info or 'filename' options = options or {} if isinstance(options, base_text_type): args = shlex.split(options) options = vars(get_opts().parse_args(args)) if default_options: if isinstance(default_options, base_text_type): default_args = shlex.split(default_options) merged_options = vars(get_opts().parse_args(default_args)) else: merged_options = deepcopy(default_options) merged_options.update(options) options = merged_options result = [] hashers = [] # Force unicode as soon as possible filename = u(filename) if isinstance(info, base_text_type): info = [info] for infotype in info: if infotype == 'filename': result.append(_guess_filename(filename, options, **kwargs)) elif infotype == 'hash_mpc': from guessit.hash_mpc import hash_file try: result.append(Guess({infotype: hash_file(filename)}, confidence=1.0)) except Exception as e: log.warning('Could not compute MPC-style hash because: %s' % e) elif infotype == 'hash_ed2k': from guessit.hash_ed2k import hash_file try: result.append(Guess({infotype: hash_file(filename)}, confidence=1.0)) except Exception as e: log.warning('Could not compute ed2k hash because: %s' % e) elif infotype.startswith('hash_'): import hashlib hashname = infotype[5:] try: hasher = getattr(hashlib, hashname)() hashers.append((infotype, hasher)) except AttributeError: log.warning('Could not compute %s hash because it is not available from python\'s hashlib module' % hashname) elif infotype == 'video': g = guess_video_metadata(filename) if g: result.append(g) else: log.warning('Invalid infotype: %s' % infotype) # do all the hashes now, but on a single pass if hashers: try: blocksize = 8192 hasherobjs = dict(hashers).values() with open(filename, 'rb') as f: chunk = f.read(blocksize) while chunk: for hasher in hasherobjs: hasher.update(chunk) chunk = f.read(blocksize) for infotype, hasher in hashers: result.append(Guess({infotype: hasher.hexdigest()}, confidence=1.0)) except Exception as e: log.warning('Could not compute hash because: %s' % e) result = smart_merge(result) return result def guess_video_info(filename, info=None, options=None, **kwargs): return guess_file_info(filename, info=info, options=options, type='video', **kwargs) def guess_movie_info(filename, info=None, options=None, **kwargs): return guess_file_info(filename, info=info, options=options, type='movie', **kwargs) def guess_episode_info(filename, info=None, options=None, **kwargs): return guess_file_info(filename, info=info, options=options, type='episode', **kwargs) guessit-0.11.0/guessit/quality.py0000664000175000017500000000430512410027040017565 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Rémi Alvergnat # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.plugins.transformers import all_transformers def best_quality_properties(props, *guesses): """Retrieve the best quality guess, based on given properties :param props: Properties to include in the rating :type props: list of strings :param guesses: Guesses to rate :type guesses: :class:`guessit.guess.Guess` :return: Best quality guess from all passed guesses :rtype: :class:`guessit.guess.Guess` """ best_guess = None best_rate = None for guess in guesses: for transformer in all_transformers(): rate = transformer.rate_quality(guess, *props) if best_rate is None or best_rate < rate: best_rate = rate best_guess = guess return best_guess def best_quality(*guesses): """Retrieve the best quality guess. :param guesses: Guesses to rate :type guesses: :class:`guessit.guess.Guess` :return: Best quality guess from all passed guesses :rtype: :class:`guessit.guess.Guess` """ best_guess = None best_rate = None for guess in guesses: for transformer in all_transformers(): rate = transformer.rate_quality(guess) if best_rate is None or best_rate < rate: best_rate = rate best_guess = guess return best_guess guessit-0.11.0/guessit/matchtree.py0000664000175000017500000004106612571103570020071 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import copy import logging import guessit # @UnusedImport needed for doctests from guessit import UnicodeMixin, base_text_type from guessit.textutils import clean_default, str_fill from guessit.patterns import group_delimiters from guessit.guess import smart_merge, Guess log = logging.getLogger(__name__) class BaseMatchTree(UnicodeMixin): """A BaseMatchTree is a tree covering the filename, where each node represents a substring in the filename and can have a ``Guess`` associated with it that contains the information that has been guessed in this node. Nodes can be further split into subnodes until a proper split has been found. Each node has the following attributes: - string = the original string of which this node represents a region - span = a pair of (begin, end) indices delimiting the substring - parent = parent node - children = list of children nodes - guess = Guess() BaseMatchTrees are displayed in the following way: >>> path = 'Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv' >>> print(guessit.IterativeMatcher(path).match_tree) 000000 1111111111111111 2222222222222222222222222222222222222222222 333 000000 0000000000111111 0000000000111111222222222222222222222222222 000 011112 011112000011111222222222222222222 000 011112222222222222 0000011112222 01112 0111 Movies/__________(____)/Dark.City.(____).DC._____.____.___.____-___.___ tttttttttt yyyy yyyy fffff ssss aaa vvvv rrr ccc Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv The last line contains the filename, which you can use a reference. The previous line contains the type of property that has been found. The line before that contains the filename, where all the found groups have been blanked. Basically, what is left on this line are the leftover groups which could not be identified. The lines before that indicate the indices of the groups in the tree. For instance, the part of the filename 'BDRip' is the leaf with index ``(2, 2, 1)`` (read from top to bottom), and its meaning is 'format' (as shown by the ``f``'s on the last-but-one line). """ def __init__(self, string='', span=None, parent=None, clean_function=None, category=None): self.string = string self.span = span or (0, len(string)) self.parent = parent self.children = [] self.guess = Guess() self._clean_value = None self._clean_function = clean_function or clean_default self.category = category @property def value(self): """Return the substring that this node matches.""" return self.string[self.span[0]:self.span[1]] @property def clean_value(self): """Return a cleaned value of the matched substring, with better presentation formatting (punctuation marks removed, duplicate spaces, ...)""" if self._clean_value is None: self._clean_value = self.clean_string(self.value) return self._clean_value def clean_string(self, string): return self._clean_function(string) @property def offset(self): return self.span[0] @property def info(self): """Return a dict containing all the info guessed by this node, subnodes included.""" result = dict(self.guess) for c in self.children: result.update(c.info) return result @property def raw(self): result = {} for guess in self.guesses: for k in guess.keys(): result[k] = guess.raw(k) return result @property def guesses(self): """ List all guesses, including children ones. :return: list of guesses objects """ result = [] if self.guess: result.append(self.guess) for c in self.children: result.extend(c.guesses) return result @property def root(self): """Return the root node of the tree.""" if not self.parent: return self return self.parent.root @property def ancestors(self): """ Retrieve all ancestors, from this node to root node. :return: a list of MatchTree objects """ ret = [self] if not self.parent: return ret parent_ancestors = self.parent.ancestors ret.extend(parent_ancestors) return ret @property def depth(self): """Return the depth of this node.""" if self.is_leaf(): return 0 return 1 + max(c.depth for c in self.children) def is_leaf(self): """Return whether this node is a leaf or not.""" return self.children == [] def add_child(self, span, category=None): """Add a new child node to this node with the given span. :param span: span of the new MatchTree :param category: category of the new MatchTree :return: A new MatchTree instance having self as a parent """ child = MatchTree(self.string, span=span, parent=self, clean_function=self._clean_function, category=category) self.children.append(child) return child def get_partition_spans(self, indices): """Return the list of absolute spans for the regions of the original string defined by splitting this node at the given indices (relative to this node) :param indices: indices of the partition spans :return: a list of tuple of the spans """ indices = sorted(indices) if indices[-1] > len(self.value): log.error('Filename: {}'.format(self.string)) log.error('Invalid call to get_partitions_spans, indices are too high: {}, len({}) == {:d}' .format(indices, self.value, len(self.value))) if indices[0] != 0: indices.insert(0, 0) if indices[-1] != len(self.value): indices.append(len(self.value)) spans = [] for start, end in zip(indices[:-1], indices[1:]): spans.append((self.offset + start, self.offset + end)) return spans def partition(self, indices, category=None): """Partition this node by splitting it at the given indices, relative to this node. :param indices: indices of the partition spans :param category: category of the new MatchTree :return: a list of created MatchTree instances """ created = [] for partition_span in self.get_partition_spans(indices): created.append(self.add_child(span=partition_span, category=category)) return created def split_on_components(self, components, category=None): offset = 0 created = [] for c in components: start = self.value.find(c, offset) end = start + len(c) created.append(self.add_child(span=(self.offset + start, self.offset + end), category=category)) offset = end return created def nodes_at_depth(self, depth): """Return all the nodes at a given depth in the tree""" if depth == 0: yield self for child in self.children: for node in child.nodes_at_depth(depth - 1): yield node @property def node_idx(self): """Return this node's index in the tree, as a tuple. If this node is the root of the tree, then return ().""" if self.parent is None: return () return self.parent.node_idx + (self.node_last_idx,) @property def node_last_idx(self): if self.parent is None: return None return self.parent.children.index(self) def node_at(self, idx): """Return the node at the given index in the subtree rooted at this node.""" if not idx: return self try: return self.children[idx[0]].node_at(idx[1:]) except IndexError: raise ValueError('Non-existent node index: %s' % (idx,)) def nodes(self): """Return a generator of all nodes and subnodes in this tree.""" yield self for child in self.children: for node in child.nodes(): yield node def leaves(self): """Return a generator over all the nodes that are leaves.""" if self.is_leaf(): yield self else: for child in self.children: for leaf in child.leaves(): yield leaf def group_node(self): return self._other_group_node(0) def previous_group_node(self): return self._other_group_node(-1) def next_group_node(self): return self._other_group_node(+1) def _other_group_node(self, offset): if len(self.node_idx) > 1: group_idx = self.node_idx[:2] if group_idx[1] + offset >= 0: other_group_idx = (group_idx[0], group_idx[1] + offset) try: other_group_node = self.root.node_at(other_group_idx) return other_group_node except ValueError: pass return None def previous_leaf(self, leaf): """Return previous leaf for this node""" return self._other_leaf(leaf, -1) def next_leaf(self, leaf): """Return next leaf for this node""" return self._other_leaf(leaf, +1) def _other_leaf(self, leaf, offset): leaves = list(self.leaves()) index = leaves.index(leaf) + offset if 0 < index < len(leaves): return leaves[index] return None def previous_leaves(self, leaf): """Return previous leaves for this node""" leaves = list(self.leaves()) index = leaves.index(leaf) if 0 < index < len(leaves): previous_leaves = leaves[:index] previous_leaves.reverse() return previous_leaves return [] def next_leaves(self, leaf): """Return next leaves for this node""" leaves = list(self.leaves()) index = leaves.index(leaf) if 0 < index < len(leaves): return leaves[index + 1:len(leaves)] return [] def to_string(self): """Return a readable string representation of this tree. The result is a multi-line string, where the lines are: - line 1 -> N-2: each line contains the nodes at the given depth in the tree - line N-2: original string where all the found groups have been blanked - line N-1: type of property that has been found - line N: the original string, which you can use a reference. """ empty_line = ' ' * len(self.string) def to_hex(x): if isinstance(x, int): return str(x) if x < 10 else chr(55 + x) return x def meaning(result): mmap = {'episodeNumber': 'E', 'season': 'S', 'extension': 'e', 'format': 'f', 'language': 'l', 'country': 'C', 'videoCodec': 'v', 'videoProfile': 'v', 'audioCodec': 'a', 'audioProfile': 'a', 'audioChannels': 'a', 'website': 'w', 'container': 'c', 'series': 'T', 'title': 't', 'date': 'd', 'year': 'y', 'releaseGroup': 'r', 'screenSize': 's', 'other': 'o' } if result is None: return ' ' for prop, l in mmap.items(): if prop in result: return l return 'x' lines = [empty_line] * (self.depth + 2) # +2: remaining, meaning lines[-2] = self.string for node in self.nodes(): if node == self: continue idx = node.node_idx depth = len(idx) - 1 if idx: lines[depth] = str_fill(lines[depth], node.span, to_hex(idx[-1])) if node.guess: lines[-2] = str_fill(lines[-2], node.span, '_') lines[-1] = str_fill(lines[-1], node.span, meaning(node.guess)) lines.append(self.string) return '\n'.join(l.rstrip() for l in lines) def __unicode__(self): return self.to_string() def __repr__(self): return '' % self.value class MatchTree(BaseMatchTree): """The MatchTree contains a few "utility" methods which are not necessary for the BaseMatchTree, but add a lot of convenience for writing higher-level rules. """ def unidentified_leaves(self, valid=lambda leaf: len(leaf.clean_value) > 0): """Return a generator of leaves that are not empty.""" for leaf in self.leaves(): if not leaf.guess and valid(leaf): yield leaf def leaves_containing(self, property_name): """Return a generator of leaves that guessed the given property.""" if isinstance(property_name, base_text_type): property_name = [property_name] for leaf in self.leaves(): for prop in property_name: if prop in leaf.guess: yield leaf break def first_leaf_containing(self, property_name): """Return the first leaf containing the given property.""" try: return next(self.leaves_containing(property_name)) except StopIteration: return None def previous_unidentified_leaves(self, node): """Return a generator of non-empty leaves that are before the given node (in the string).""" node_idx = node.node_idx for leaf in self.unidentified_leaves(): if leaf.node_idx < node_idx: yield leaf def previous_leaves_containing(self, node, property_name): """Return a generator of leaves containing the given property that are before the given node (in the string).""" node_idx = node.node_idx for leaf in self.leaves_containing(property_name): if leaf.node_idx < node_idx: yield leaf def is_explicit(self): """Return whether the group was explicitly enclosed by parentheses/square brackets/etc.""" return (self.value[0] + self.value[-1]) in group_delimiters def matched(self): """Return a single guess that contains all the info found in the nodes of this tree, trying to merge properties as good as possible. """ if not getattr(self, '_matched_result', None): # we need to make a copy here, as the merge functions work in place and # calling them on the match tree would modify it parts = [copy.copy(node.guess) for node in self.nodes() if node.guess] result = smart_merge(parts) log.debug('Final result: ' + result.nice_string()) self._matched_result = result for leaf in self.unidentified_leaves(): if 'unidentified' not in self._matched_result: self._matched_result['unidentified'] = [] self._matched_result['unidentified'].append(leaf.clean_value) return self._matched_result guessit-0.11.0/guessit/transfo/0000775000175000017500000000000012572402126017210 5ustar toilaltoilal00000000000000guessit-0.11.0/guessit/transfo/expected_title.py0000664000175000017500000000474012534564763022607 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import re from guessit.containers import PropertiesContainer from guessit.matcher import GuessFinder from guessit.plugins.transformers import Transformer class ExpectedTitle(Transformer): def __init__(self): Transformer.__init__(self, 225) def register_arguments(self, opts, naming_opts, output_opts, information_opts, webservice_opts, other_options): naming_opts.add_argument('-T', '--expected-title', action='append', dest='expected_title', help='Expected title (can be used multiple times)') def should_process(self, mtree, options=None): return options and options.get('expected_title') @staticmethod def expected_titles(string, node=None, options=None): container = PropertiesContainer(enhance=True, canonical_from_pattern=False) for expected_title in options.get('expected_title'): if expected_title.startswith('re:'): expected_title = expected_title[3:] expected_title = expected_title.replace(' ', '-') container.register_property('title', expected_title, enhance=True) else: expected_title = re.escape(expected_title) container.register_property('title', expected_title, enhance=False) found = container.find_properties(string, node, options) return container.as_guess(found, string) def supported_properties(self): return ['title'] def process(self, mtree, options=None): GuessFinder(self.expected_titles, None, self.log, options).process_nodes(mtree.unidentified_leaves()) guessit-0.11.0/guessit/transfo/guess_properties.py0000664000175000017500000004066512571103762023202 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Rémi Alvergnat # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import re from guessit.containers import PropertiesContainer, WeakValidator, LeavesValidator, QualitiesContainer, ChainedValidator, DefaultValidator, OnlyOneValidator, LeftValidator, NeighborValidator, FullMatchValidator from guessit.patterns import sep, build_or_pattern from guessit.patterns.extension import subtitle_exts, video_exts, info_exts from guessit.patterns.numeral import numeral, parse_numeral from guessit.plugins.transformers import Transformer from guessit.matcher import GuessFinder, found_property class GuessProperties(Transformer): def __init__(self): Transformer.__init__(self, 35) self.container = PropertiesContainer() self.qualities = QualitiesContainer() def register_property(propname, props, **kwargs): """props a dict of {value: [patterns]}""" for canonical_form, patterns in props.items(): if isinstance(patterns, tuple): patterns2, pattern_kwarg = patterns if kwargs: current_kwarg = dict(kwargs) current_kwarg.update(pattern_kwarg) else: current_kwarg = dict(pattern_kwarg) current_kwarg['canonical_form'] = canonical_form self.container.register_property(propname, *patterns2, **current_kwarg) elif kwargs: current_kwarg = dict(kwargs) current_kwarg['canonical_form'] = canonical_form self.container.register_property(propname, *patterns, **current_kwarg) else: self.container.register_property(propname, *patterns, canonical_form=canonical_form) def register_quality(propname, quality_dict): """props a dict of {canonical_form: quality}""" for canonical_form, quality in quality_dict.items(): self.qualities.register_quality(propname, canonical_form, quality) # http://en.wikipedia.org/wiki/Pirated_movie_release_types register_property('format', {'VHS': ['VHS', 'VHS-Rip'], 'Cam': ['CAM', 'CAMRip', 'HD-CAM'], #'Telesync': ['TELESYNC', 'PDVD'], 'Telesync': (['TS', 'HD-TS'], {'confidence': 0.4}), 'Workprint': ['WORKPRINT', 'WP'], 'Telecine': ['TELECINE', 'TC'], 'PPV': ['PPV', 'PPV-Rip'], # Pay Per View 'TV': ['SD-TV', 'SD-TV-Rip', 'Rip-SD-TV', 'TV-Rip', 'Rip-TV'], 'DVB': ['DVB-Rip', 'DVB', 'PD-TV'], 'DVD': ['DVD', 'DVD-Rip', 'VIDEO-TS', 'DVD-R', 'DVD-9', 'DVD-5'], 'HDTV': ['HD-TV', 'TV-RIP-HD', 'HD-TV-RIP', 'HD-RIP'], 'VOD': ['VOD', 'VOD-Rip'], 'WEBRip': ['WEB-Rip'], 'WEB-DL': ['WEB-DL', 'WEB-HD', 'WEB'], 'HD-DVD': ['HD-DVD-Rip', 'HD-DVD'], 'BluRay': ['Blu-ray(?:-Rip)?', 'B[DR]', 'B[DR]-Rip', 'BD[59]', 'BD25', 'BD50'] }) register_quality('format', {'VHS': -100, 'Cam': -90, 'Telesync': -80, 'Workprint': -70, 'Telecine': -60, 'PPV': -50, 'TV': -30, 'DVB': -20, 'DVD': 0, 'HDTV': 20, 'VOD': 40, 'WEBRip': 50, 'WEB-DL': 60, 'HD-DVD': 80, 'BluRay': 100 }) register_property('screenSize', {'360p': ['(?:\d{3,}(?:\\|\/|x|\*))?360(?:i|p?x?)'], '368p': ['(?:\d{3,}(?:\\|\/|x|\*))?368(?:i|p?x?)'], '480p': ['(?:\d{3,}(?:\\|\/|x|\*))?480(?:i|p?x?)'], #'480p': (['hr'], {'confidence': 0.2}), # duplicate dict key '576p': ['(?:\d{3,}(?:\\|\/|x|\*))?576(?:i|p?x?)'], '720p': ['(?:\d{3,}(?:\\|\/|x|\*))?720(?:i|p?x?)'], '900p': ['(?:\d{3,}(?:\\|\/|x|\*))?900(?:i|p?x?)'], '1080i': ['(?:\d{3,}(?:\\|\/|x|\*))?1080i'], '1080p': ['(?:\d{3,}(?:\\|\/|x|\*))?1080p?x?'], '4K': ['(?:\d{3,}(?:\\|\/|x|\*))?2160(?:i|p?x?)'] }, validator=ChainedValidator(DefaultValidator(), OnlyOneValidator())) _digits_re = re.compile('\d+') def resolution_formatter(value): digits = _digits_re.findall(value) return 'x'.join(digits) self.container.register_property('screenSize', '\d{3,4}-?[x\*]-?\d{3,4}', canonical_from_pattern=False, formatter=resolution_formatter) register_quality('screenSize', {'360p': -300, '368p': -200, '480p': -100, '576p': 0, '720p': 100, '900p': 130, '1080i': 180, '1080p': 200, '4K': 400 }) _videoCodecProperty = {'Real': ['Rv\d{2}'], # http://en.wikipedia.org/wiki/RealVideo 'Mpeg2': ['Mpeg2'], 'DivX': ['DVDivX', 'DivX'], 'XviD': ['XviD'], 'h264': ['[hx]-264(?:-AVC)?', 'MPEG-4(?:-AVC)'], 'h265': ['[hx]-265(?:-HEVC)?', 'HEVC'] } register_property('videoCodec', _videoCodecProperty) register_quality('videoCodec', {'Real': -50, 'Mpeg2': -30, 'DivX': -10, 'XviD': 0, 'h264': 100, 'h265': 150 }) # http://blog.mediacoderhq.com/h264-profiles-and-levels/ # http://fr.wikipedia.org/wiki/H.264 self.container.register_property('videoProfile', 'BP', validator=LeavesValidator(lambdas=[lambda node: 'videoCodec' in node.guess])) self.container.register_property('videoProfile', 'XP', 'EP', canonical_form='XP', validator=LeavesValidator(lambdas=[lambda node: 'videoCodec' in node.guess])) self.container.register_property('videoProfile', 'MP', validator=LeavesValidator(lambdas=[lambda node: 'videoCodec' in node.guess])) self.container.register_property('videoProfile', 'HP', 'HiP', canonical_form='HP', validator=LeavesValidator(lambdas=[lambda node: 'videoCodec' in node.guess])) self.container.register_property('videoProfile', '10.?bit', 'Hi10P', canonical_form='10bit') self.container.register_property('videoProfile', '8.?bit', canonical_form='8bit') self.container.register_property('videoProfile', 'Hi422P', validator=LeavesValidator(lambdas=[lambda node: 'videoCodec' in node.guess])) self.container.register_property('videoProfile', 'Hi444PP', validator=LeavesValidator(lambdas=[lambda node: 'videoCodec' in node.guess])) register_quality('videoProfile', {'BP': -20, 'XP': -10, 'MP': 0, 'HP': 10, '10bit': 15, 'Hi422P': 25, 'Hi444PP': 35 }) # has nothing to do here (or on filenames for that matter), but some # releases use it and it helps to identify release groups, so we adapt register_property('videoApi', {'DXVA': ['DXVA']}) register_property('audioCodec', {'MP3': ['MP3', 'LAME', 'LAME(?:\d)+-(?:\d)+'], 'DolbyDigital': ['DD'], 'AAC': ['AAC'], 'AC3': ['AC3'], 'Flac': ['FLAC'], 'DTS': (['DTS'], {'validator': LeftValidator()}), 'TrueHD': ['True-HD'] }) register_quality('audioCodec', {'MP3': 10, 'DolbyDigital': 30, 'AAC': 35, 'AC3': 40, 'Flac': 45, 'DTS': 60, 'TrueHD': 70 }) self.container.register_property('audioProfile', 'HD', validator=LeavesValidator(lambdas=[lambda node: node.guess.get('audioCodec') == 'DTS'])) self.container.register_property('audioProfile', 'HD-MA', canonical_form='HDMA', validator=LeavesValidator(lambdas=[lambda node: node.guess.get('audioCodec') == 'DTS'])) self.container.register_property('audioProfile', 'HE', validator=LeavesValidator(lambdas=[lambda node: node.guess.get('audioCodec') == 'AAC'])) self.container.register_property('audioProfile', 'LC', validator=LeavesValidator(lambdas=[lambda node: node.guess.get('audioCodec') == 'AAC'])) self.container.register_property('audioProfile', 'HQ', validator=LeavesValidator(lambdas=[lambda node: node.guess.get('audioCodec') == 'AC3'])) register_quality('audioProfile', {'HD': 20, 'HDMA': 50, 'LC': 0, 'HQ': 0, 'HE': 20 }) register_property('audioChannels', {'7.1': ['7[\W_]1', '7ch', '8ch'], '5.1': ['5[\W_]1', '5ch', '6ch'], '2.0': ['2[\W_]0', '2ch', 'stereo'], '1.0': ['1[\W_]0', '1ch', 'mono'] }) register_quality('audioChannels', {'7.1': 200, '5.1': 100, '2.0': 0, '1.0': -100 }) self.container.register_property('episodeFormat', r'Minisodes?', canonical_form='Minisode') self.container.register_property('crc32', '(?:[a-fA-F]|[0-9]){8}', enhance=False, canonical_from_pattern=False) part_words = ['pt', 'part'] self.container.register_property(None, '(' + build_or_pattern(part_words) + sep + '?(?P' + numeral + '))[^0-9]', enhance=False, canonical_from_pattern=False, confidence=0.4, formatter=parse_numeral) register_property('other', {'AudioFix': ['Audio-Fix', 'Audio-Fixed'], 'SyncFix': ['Sync-Fix', 'Sync-Fixed'], 'DualAudio': ['Dual-Audio'], 'WideScreen': ['ws', 'wide-screen'], 'Netflix': ['Netflix', 'NF'] }) self.container.register_property('other', 'Real', 'Fix', canonical_form='Proper', validator=ChainedValidator(FullMatchValidator(), NeighborValidator())) self.container.register_property('other', 'Proper', 'Repack', 'Rerip', canonical_form='Proper') self.container.register_property('other', 'Fansub', canonical_form='Fansub', validator=ChainedValidator(FullMatchValidator(), NeighborValidator())) self.container.register_property('other', 'Fastsub', canonical_form='Fastsub', validator=ChainedValidator(FullMatchValidator(), NeighborValidator())) self.container.register_property('other', '(?:Seasons?' + sep + '?)?Complete', canonical_form='Complete') self.container.register_property('other', 'R5', 'RC', canonical_form='R5') self.container.register_property('other', 'Pre-Air', 'Preair', canonical_form='Preair') self.container.register_property('other', 'CC') # Close Caption self.container.register_property('other', 'LD', 'MD') # Line/Mic Dubbed self.container.register_canonical_properties('other', 'Screener', 'Remux', '3D', 'HD', 'mHD', 'HDLight', 'HQ', 'DDC', 'HR', 'PAL', 'SECAM', 'NTSC') self.container.register_canonical_properties('other', 'Limited', 'Complete', 'Classic', 'Unrated', 'LiNE', 'Bonus', 'Trailer', validator=WeakValidator()) for prop in self.container.get_properties('format'): self.container.register_property('other', prop.pattern + '(-?Scr(?:eener)?)', canonical_form='Screener') for exts in (subtitle_exts, info_exts, video_exts): for container in exts: self.container.register_property('container', container, confidence=0.3) def guess_properties(self, string, node=None, options=None): found = self.container.find_properties(string, node, options) guess = self.container.as_guess(found, string) if guess and node: if 'part' in guess: # If two guesses contains both part in same group, create an partList for existing_guess in node.group_node().guesses: if 'part' in existing_guess: if 'partList' not in existing_guess: existing_guess['partList'] = [existing_guess['part']] existing_guess['partList'].append(guess['part']) existing_guess['partList'].sort() if existing_guess['part'] > guess['part']: existing_guess.set_confidence('part', 0) else: guess.set_confidence('part', 0) guess['partList'] = list(existing_guess['partList']) return guess def supported_properties(self): supported_properties = list(self.container.get_supported_properties()) supported_properties.append('partList') return supported_properties def process(self, mtree, options=None): GuessFinder(self.guess_properties, 1.0, self.log, options).process_nodes(mtree.unidentified_leaves()) proper_count = 0 for other_leaf in mtree.leaves_containing('other'): if 'other' in other_leaf.info and 'Proper' in other_leaf.info['other']: proper_count += 1 if proper_count: found_property(mtree, 'properCount', proper_count) def rate_quality(self, guess, *props): return self.qualities.rate_quality(guess, *props) guessit-0.11.0/guessit/transfo/expected_series.py0000664000175000017500000000476112534564763022763 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import re from guessit.containers import PropertiesContainer from guessit.matcher import GuessFinder from guessit.plugins.transformers import Transformer class ExpectedSeries(Transformer): def __init__(self): Transformer.__init__(self, 230) def register_arguments(self, opts, naming_opts, output_opts, information_opts, webservice_opts, other_options): naming_opts.add_argument('-S', '--expected-series', action='append', dest='expected_series', help='Expected series to parse (can be used multiple times)') def should_process(self, mtree, options=None): return options and options.get('expected_series') @staticmethod def expected_series(string, node=None, options=None): container = PropertiesContainer(enhance=True, canonical_from_pattern=False) for expected_serie in options.get('expected_series'): if expected_serie.startswith('re:'): expected_serie = expected_serie[3:] expected_serie = expected_serie.replace(' ', '-') container.register_property('series', expected_serie, enhance=True) else: expected_serie = re.escape(expected_serie) container.register_property('series', expected_serie, enhance=False) found = container.find_properties(string, node, options) return container.as_guess(found, string) def supported_properties(self): return ['series'] def process(self, mtree, options=None): GuessFinder(self.expected_series, None, self.log, options).process_nodes(mtree.unidentified_leaves()) guessit-0.11.0/guessit/transfo/guess_episode_info_from_position.py0000664000175000017500000002310312572400010026367 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.plugins.transformers import Transformer, get_transformer from guessit.textutils import reorder_title from guessit.matcher import found_property from guessit.patterns.list import all_separators from guessit.language import all_lang_prefixes_suffixes class GuessEpisodeInfoFromPosition(Transformer): def __init__(self): Transformer.__init__(self, -200) def supported_properties(self): return ['title', 'series'] @staticmethod def excluded_word(*values): for value in values: if value.clean_value.lower() in (all_separators + all_lang_prefixes_suffixes): return True return False def match_from_epnum_position(self, path_node, ep_node, options): epnum_idx = ep_node.node_idx # a few helper functions to be able to filter using high-level semantics def before_epnum_in_same_pathgroup(): return [leaf for leaf in path_node.unidentified_leaves(lambda x: len(x.clean_value) > 1) if (leaf.node_idx[0] == epnum_idx[0] and leaf.node_idx[1:] < epnum_idx[1:] and not GuessEpisodeInfoFromPosition.excluded_word(leaf))] def after_epnum_in_same_pathgroup(): return [leaf for leaf in path_node.unidentified_leaves(lambda x: len(x.clean_value) > 1) if (leaf.node_idx[0] == epnum_idx[0] and leaf.node_idx[1:] > epnum_idx[1:] and not GuessEpisodeInfoFromPosition.excluded_word(leaf))] def after_epnum_in_same_explicitgroup(): return [leaf for leaf in path_node.unidentified_leaves(lambda x: len(x.clean_value) > 1) if (leaf.node_idx[:2] == epnum_idx[:2] and leaf.node_idx[2:] > epnum_idx[2:] and not GuessEpisodeInfoFromPosition.excluded_word(leaf))] # epnumber is the first group and there are only 2 after it in same # path group # -> series title - episode title title_candidates = GuessEpisodeInfoFromPosition._filter_candidates(after_epnum_in_same_pathgroup(), options) if ('title' not in path_node.info and # no title 'series' in path_node.info and # series present before_epnum_in_same_pathgroup() == [] and # no groups before len(title_candidates) == 1): # only 1 group after found_property(title_candidates[0], 'title', confidence=0.4) return if ('title' not in path_node.info and # no title before_epnum_in_same_pathgroup() == [] and # no groups before len(title_candidates) == 2): # only 2 groups after found_property(title_candidates[0], 'series', confidence=0.4) found_property(title_candidates[1], 'title', confidence=0.4) return # if we have at least 1 valid group before the episodeNumber, then it's # probably the series name series_candidates = before_epnum_in_same_pathgroup() if len(series_candidates) >= 1: found_property(series_candidates[0], 'series', confidence=0.7) # only 1 group after (in the same path group) and it's probably the # episode title. title_candidates = GuessEpisodeInfoFromPosition._filter_candidates(after_epnum_in_same_pathgroup(), options) if len(title_candidates) == 1: found_property(title_candidates[0], 'title', confidence=0.5) return else: # try in the same explicit group, with lower confidence title_candidates = GuessEpisodeInfoFromPosition._filter_candidates(after_epnum_in_same_explicitgroup(), options) if len(title_candidates) == 1: found_property(title_candidates[0], 'title', confidence=0.4) return elif len(title_candidates) > 1: found_property(title_candidates[0], 'title', confidence=0.3) return # get the one with the longest value title_candidates = GuessEpisodeInfoFromPosition._filter_candidates(after_epnum_in_same_pathgroup(), options) if title_candidates: maxidx = -1 maxv = -1 for i, c in enumerate(title_candidates): if len(c.clean_value) > maxv: maxidx = i maxv = len(c.clean_value) if maxidx > -1: found_property(title_candidates[maxidx], 'title', confidence=0.3) def should_process(self, mtree, options=None): options = options or {} return not options.get('skip_title') and mtree.guess.get('type', '').startswith('episode') @staticmethod def _filter_candidates(candidates, options): episode_details_transformer = get_transformer('guess_episode_details') if episode_details_transformer: candidates = [n for n in candidates if not episode_details_transformer.container.find_properties(n.value, n, options, re_match=True)] candidates = list(filter(lambda n: not GuessEpisodeInfoFromPosition.excluded_word(n), candidates)) return candidates def process(self, mtree, options=None): """ try to identify the remaining unknown groups by looking at their position relative to other known elements """ eps = [node for node in mtree.leaves() if 'episodeNumber' in node.guess] if not eps: eps = [node for node in mtree.leaves() if 'date' in node.guess] eps = sorted(eps, key=lambda ep: -ep.guess.confidence()) if eps: performed_path_nodes = [] for ep_node in eps: # Perform only first episode node for each path node path_node = [node for node in ep_node.ancestors if node.category == 'path'] if len(path_node) > 0: path_node = path_node[0] else: path_node = ep_node.root if path_node not in performed_path_nodes: self.match_from_epnum_position(path_node, ep_node, options) performed_path_nodes.append(path_node) else: # if we don't have the episode number, but at least 2 groups in the # basename, then it's probably series - eptitle basename = list(filter(lambda x: x.category == 'path', mtree.nodes()))[-2] title_candidates = GuessEpisodeInfoFromPosition._filter_candidates(basename.unidentified_leaves(), options) if len(title_candidates) >= 2 and 'series' not in mtree.info: found_property(title_candidates[0], 'series', confidence=0.4) found_property(title_candidates[1], 'title', confidence=0.4) elif len(title_candidates) == 1: # but if there's only one candidate, it's probably the series name found_property(title_candidates[0], 'series' if 'series' not in mtree.info else 'title', confidence=0.4) # if we only have 1 remaining valid group in the folder containing the # file, then it's likely that it is the series name path_nodes = list(filter(lambda x: x.category == 'path', mtree.nodes())) try: series_candidates = list(path_nodes[-3].unidentified_leaves()) except IndexError: series_candidates = [] if len(series_candidates) == 1 and not GuessEpisodeInfoFromPosition.excluded_word(series_candidates[0]): found_property(series_candidates[0], 'series', confidence=0.3) # if there's a path group that only contains the season info, then the # previous one is most likely the series title (ie: ../series/season X/..) eps = [node for node in mtree.nodes() if 'season' in node.guess and 'episodeNumber' not in node.guess] if eps: previous = [node for node in mtree.unidentified_leaves() if node.node_idx[0] == eps[0].node_idx[0] - 1] if len(previous) == 1 and not GuessEpisodeInfoFromPosition.excluded_word(previous[0]): found_property(previous[0], 'series', confidence=0.5) # If we have found title without any serie name, replace it by the serie name. if 'series' not in mtree.info and 'title' in mtree.info: title_leaf = mtree.first_leaf_containing('title') metadata = title_leaf.guess.metadata('title') value = title_leaf.guess['title'] del title_leaf.guess['title'] title_leaf.guess.set('series', value, metadata=metadata) def post_process(self, mtree, options=None): for node in mtree.nodes(): if 'series' not in node.guess: continue node.guess['series'] = reorder_title(node.guess['series']) guessit-0.11.0/guessit/transfo/guess_filetype.py0000664000175000017500000002723012571103570022615 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import mimetypes import os.path import re from guessit.guess import Guess from guessit.patterns.extension import subtitle_exts, info_exts, video_exts from guessit.transfo import TransformerException from guessit.plugins.transformers import Transformer, get_transformer from guessit.matcher import log_found_guess, found_guess class GuessFiletype(Transformer): def __init__(self): Transformer.__init__(self, 200) # List of well known movies and series, hardcoded because they cannot be # guessed appropriately otherwise MOVIES = ['OSS 117'] SERIES = ['Band of Brothers'] MOVIES = [m.lower() for m in MOVIES] SERIES = [s.lower() for s in SERIES] def guess_filetype(self, mtree, options=None): options = options or {} # put the filetype inside a dummy container to be able to have the # following functions work correctly as closures # this is a workaround for python 2 which doesn't have the # 'nonlocal' keyword which we could use here in the upgrade_* functions # (python 3 does have it) filetype_container = [mtree.guess.get('type')] other = {} filename = mtree.string def upgrade_episode(): if filetype_container[0] == 'subtitle': filetype_container[0] = 'episodesubtitle' elif filetype_container[0] == 'info': filetype_container[0] = 'episodeinfo' elif (not filetype_container[0] or filetype_container[0] == 'video'): filetype_container[0] = 'episode' def upgrade_movie(): if filetype_container[0] == 'subtitle': filetype_container[0] = 'moviesubtitle' elif filetype_container[0] == 'info': filetype_container[0] = 'movieinfo' elif (not filetype_container[0] or filetype_container[0] == 'video'): filetype_container[0] = 'movie' def upgrade_subtitle(): if filetype_container[0] == 'movie': filetype_container[0] = 'moviesubtitle' elif filetype_container[0] == 'episode': filetype_container[0] = 'episodesubtitle' elif not filetype_container[0]: filetype_container[0] = 'subtitle' def upgrade_info(): if filetype_container[0] == 'movie': filetype_container[0] = 'movieinfo' elif filetype_container[0] == 'episode': filetype_container[0] = 'episodeinfo' elif not filetype_container[0]: filetype_container[0] = 'info' # look at the extension first fileext = os.path.splitext(filename)[1][1:].lower() if fileext in subtitle_exts: upgrade_subtitle() other = {'container': fileext} elif fileext in info_exts: upgrade_info() other = {'container': fileext} elif fileext in video_exts: other = {'container': fileext} else: if fileext and not options.get('name_only'): other = {'extension': fileext} list(mtree.unidentified_leaves())[-1].guess = Guess(other) # check whether we are in a 'Movies', 'Tv Shows', ... folder folder_rexps = [(r'Movies?', upgrade_movie), (r'Films?', upgrade_movie), (r'Tv[ _-]?Shows?', upgrade_episode), (r'Series?', upgrade_episode), (r'Episodes?', upgrade_episode)] for frexp, upgrade_func in folder_rexps: frexp = re.compile(frexp, re.IGNORECASE) for pathgroup in mtree.children: if frexp.match(pathgroup.value): upgrade_func() return filetype_container[0], other # check for a few specific cases which will unintentionally make the # following heuristics confused (eg: OSS 117 will look like an episode, # season 1, epnum 17, when it is in fact a movie) fname = mtree.clean_string(filename).lower() for m in self.MOVIES: if m in fname: self.log.debug('Found in exception list of movies -> type = movie') upgrade_movie() return filetype_container[0], other for s in self.SERIES: if s in fname: self.log.debug('Found in exception list of series -> type = episode') upgrade_episode() return filetype_container[0], other # if we have an episode_rexp (eg: s02e13), it is an episode episode_transformer = get_transformer('guess_episodes_rexps') if episode_transformer: filename_parts = list(x.value for x in mtree.unidentified_leaves()) filename_parts.append(filename) for filename_part in filename_parts: guess = episode_transformer.guess_episodes_rexps(filename_part) if guess: self.log.debug('Found guess_episodes_rexps: %s -> type = episode', guess) upgrade_episode() return filetype_container[0], other properties_transformer = get_transformer('guess_properties') if properties_transformer: # if we have certain properties characteristic of episodes, it is an ep found = properties_transformer.container.find_properties(filename, mtree, options, 'episodeFormat') guess = properties_transformer.container.as_guess(found, filename) if guess: self.log.debug('Found characteristic property of episodes: %s"', guess) upgrade_episode() return filetype_container[0], other weak_episode_transformer = get_transformer('guess_weak_episodes_rexps') if weak_episode_transformer: found = weak_episode_transformer.container.find_properties(filename, mtree, options, 'episodeNumber') guess = weak_episode_transformer.container.as_guess(found, filename) if guess and (guess.raw('episodeNumber')[0] == '0' or guess['episodeNumber'] >= 10): self.log.debug('Found characteristic property of episodes: %s"', guess) upgrade_episode() return filetype_container[0], other found = properties_transformer.container.find_properties(filename, mtree, options, 'crc32') guess = properties_transformer.container.as_guess(found, filename) if guess: found = weak_episode_transformer.container.find_properties(filename, mtree, options) guess = weak_episode_transformer.container.as_guess(found, filename) if guess: self.log.debug('Found characteristic property of episodes: %s"', guess) upgrade_episode() return filetype_container[0], other found = properties_transformer.container.find_properties(filename, mtree, options, 'format') guess = properties_transformer.container.as_guess(found, filename) if guess and guess['format'] in ('HDTV', 'WEBRip', 'WEB-DL', 'DVB'): # Use weak episodes only if TV or WEB source weak_episode_transformer = get_transformer('guess_weak_episodes_rexps') if weak_episode_transformer: guess = weak_episode_transformer.guess_weak_episodes_rexps(filename) if guess: self.log.debug('Found guess_weak_episodes_rexps: %s -> type = episode', guess) upgrade_episode() return filetype_container[0], other website_transformer = get_transformer('guess_website') if website_transformer: found = website_transformer.container.find_properties(filename, mtree, options, 'website') guess = website_transformer.container.as_guess(found, filename) if guess: for namepart in ('tv', 'serie', 'episode'): if namepart in guess['website']: # origin-specific type self.log.debug('Found characteristic property of episodes: %s', guess) upgrade_episode() return filetype_container[0], other if filetype_container[0] in ('subtitle', 'info') or (not filetype_container[0] and fileext in video_exts): # if no episode info found, assume it's a movie self.log.debug('Nothing characteristic found, assuming type = movie') upgrade_movie() if not filetype_container[0]: self.log.debug('Nothing characteristic found, assuming type = unknown') filetype_container[0] = 'unknown' return filetype_container[0], other def process(self, mtree, options=None): """guess the file type now (will be useful later) """ filetype, other = self.guess_filetype(mtree, options) mtree.guess.set('type', filetype, confidence=1.0) log_found_guess(mtree.guess) filetype_info = Guess(other, confidence=1.0) # guess the mimetype of the filename # TODO: handle other mimetypes not found on the default type_maps # mimetypes.types_map['.srt']='text/subtitle' mime, _ = mimetypes.guess_type(mtree.string, strict=False) if mime is not None: filetype_info.update({'mimetype': mime}, confidence=1.0) # Retrieve the last node of category path (extension node) node_ext = list(filter(lambda x: x.category == 'path', mtree.nodes()))[-1] found_guess(node_ext, filetype_info) if mtree.guess.get('type') in [None, 'unknown']: if options.get('name_only'): mtree.guess.set('type', 'movie', confidence=0.6) else: raise TransformerException(__name__, 'Unknown file type') def second_pass_options(self, mtree, options=None): if 'type' not in options or not options['type']: if mtree.info.get('type') != 'episode': # now look whether there are some specific hints for episode vs movie # If we have a date and no year, this is a TV Show. if 'date' in mtree.info and 'year' not in mtree.info: return {'type': 'episode'} if mtree.info.get('type') != 'movie': # If we have a year, no season but raw episodeNumber is a number not starting with '0', this is a movie. if 'year' in mtree.info and 'episodeNumber' in mtree.info and not 'season' in mtree.info: try: int(mtree.raw['episodeNumber']) return {'type': 'movie'} except ValueError: pass guessit-0.11.0/guessit/transfo/guess_year.py0000664000175000017500000000460212571103570021732 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.plugins.transformers import Transformer from guessit.matcher import GuessFinder from guessit.date import search_year, valid_year class GuessYear(Transformer): def __init__(self): Transformer.__init__(self, -160) def supported_properties(self): return ['year'] @staticmethod def guess_year(string, node=None, options=None): year, span = search_year(string) if year: return {'year': year}, span else: return None, None def second_pass_options(self, mtree, options=None): year_nodes = list(mtree.leaves_containing('year')) # if we found a year, let's try by ignoring all instances of that year # as a candidate, let's take the one that appears last in the filename if year_nodes: year_candidate = year_nodes[-1].guess['year'] year_nodes = [year for year in year_nodes if year.guess['year'] != year_candidate] if year_nodes: return {'skip_nodes': year_nodes} return None def process(self, mtree, options=None): GuessFinder(self.guess_year, 1.0, self.log, options).process_nodes(mtree.unidentified_leaves()) # if we found a season number that is a valid year, it is usually safe to assume # we can also set the year property to that value for n in mtree.leaves_containing('season'): g = n.guess season = g['season'] if valid_year(season): g['year'] = season guessit-0.11.0/guessit/transfo/guess_idnumber.py0000664000175000017500000000531112534564763022613 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import re from guessit.plugins.transformers import Transformer from guessit.matcher import GuessFinder _DIGIT = 0 _LETTER = 1 _OTHER = 2 class GuessIdnumber(Transformer): def __init__(self): Transformer.__init__(self, 220) def supported_properties(self): return ['idNumber'] _idnum = re.compile(r'(?P[a-zA-Z0-9-]{20,})') # 1.0, (0, 0)) def guess_idnumber(self, string, node=None, options=None): match = self._idnum.search(string) if match is not None: result = match.groupdict() switch_count = 0 switch_letter_count = 0 letter_count = 0 last_letter = None last = _LETTER for c in result['idNumber']: if c in '0123456789': ci = _DIGIT elif c in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ': ci = _LETTER if c != last_letter: switch_letter_count += 1 last_letter = c letter_count += 1 else: ci = _OTHER if ci != last: switch_count += 1 last = ci switch_ratio = float(switch_count) / len(result['idNumber']) letters_ratio = (float(switch_letter_count) / letter_count) if letter_count > 0 else 1 # only return the result as probable if we alternate often between # char type (more likely for hash values than for common words) if switch_ratio > 0.4 and letters_ratio > 0.4: return result, match.span() return None, None def process(self, mtree, options=None): GuessFinder(self.guess_idnumber, 0.4, self.log, options).process_nodes(mtree.unidentified_leaves()) guessit-0.11.0/guessit/transfo/guess_language.py0000664000175000017500000002060512572372140022560 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.language import search_language, subtitle_prefixes, subtitle_suffixes from guessit.patterns.extension import subtitle_exts from guessit.textutils import find_words from guessit.plugins.transformers import Transformer from guessit.matcher import GuessFinder class GuessLanguage(Transformer): def __init__(self): Transformer.__init__(self, 30) def register_arguments(self, opts, naming_opts, output_opts, information_opts, webservice_opts, other_options): naming_opts.add_argument('-L', '--allowed-languages', action='append', dest='allowed_languages', help='Allowed language (can be used multiple times)') def supported_properties(self): return ['language', 'subtitleLanguage'] @staticmethod def guess_language(string, node=None, options=None): allowed_languages = None if options and 'allowed_languages' in options: allowed_languages = options.get('allowed_languages') directory = list(filter(lambda x: x.category == 'path', node.ancestors))[0] if len(directory.clean_value) <= 3: # skip if we have a langage code as directory return None guess = search_language(string, allowed_languages) return guess @staticmethod def _skip_language_on_second_pass(mtree, node): """Check if found node is a valid language node, or if it's a false positive. :param mtree: Tree detected on first pass. :type mtree: :class:`guessit.matchtree.MatchTree` :param node: Node that contains a language Guess :type node: :class:`guessit.matchtree.MatchTree` :return: True if a second pass skipping this node is required :rtype: bool """ unidentified_starts = {} unidentified_ends = {} property_starts = {} property_ends = {} title_starts = {} title_ends = {} for unidentified_node in mtree.unidentified_leaves(): if len(unidentified_node.clean_value) > 1: # only consider unidentified leaves that have some meaningful content unidentified_starts[unidentified_node.span[0]] = unidentified_node unidentified_ends[unidentified_node.span[1]] = unidentified_node for property_node in mtree.leaves_containing('year'): property_starts[property_node.span[0]] = property_node property_ends[property_node.span[1]] = property_node for title_node in mtree.leaves_containing(['title', 'series']): title_starts[title_node.span[0]] = title_node title_ends[title_node.span[1]] = title_node return (node.span[0] in title_ends.keys() and (node.span[1] in unidentified_starts.keys() or node.span[1] + 1 in property_starts.keys()) or node.span[1] in title_starts.keys() and (node.span[0] == node.group_node().span[0] or node.span[0] in unidentified_ends.keys() or node.span[0] in property_ends.keys())) def second_pass_options(self, mtree, options=None): m = mtree.matched() to_skip_langs = set() for lang_key in ('language', 'subtitleLanguage'): lang_nodes = set(mtree.leaves_containing(lang_key)) for lang_node in lang_nodes: if self._skip_language_on_second_pass(mtree, lang_node): # Language probably split the title. Add to skip for 2nd pass. # if filetype is subtitle and the language appears last, just before # the extension, then it is likely a subtitle language parts = mtree.clean_string(lang_node.root.value).split() if m.get('type') in ['moviesubtitle', 'episodesubtitle']: if (lang_node.value in parts and parts.index(lang_node.value) == len(parts) - 2): continue to_skip_langs.add(lang_node.value) if to_skip_langs: # Also skip same value nodes lang_nodes = (set(mtree.leaves_containing('language')) | set(mtree.leaves_containing('subtitleLanguage'))) to_skip = [node for node in lang_nodes if node.value in to_skip_langs] return {'skip_nodes': to_skip} return None def should_process(self, mtree, options=None): options = options or {} return options.get('language', True) def process(self, mtree, options=None): GuessFinder(self.guess_language, None, self.log, options).process_nodes(mtree.unidentified_leaves()) @staticmethod def promote_subtitle(node): if 'language' in node.guess: node.guess.set('subtitleLanguage', node.guess['language'], confidence=node.guess.confidence('language')) del node.guess['language'] def post_process(self, mtree, options=None): # 1- try to promote language to subtitle language where it makes sense prefixes = [] for node in mtree.nodes(): if 'language' not in node.guess: continue # - if we matched a language in a file with a sub extension and that # the group is the last group of the filename, it is probably the # language of the subtitle # (eg: 'xxx.english.srt') ext_node = list(filter(lambda x: x.category == 'path', mtree.nodes()))[-1] if (ext_node.value.lower() in subtitle_exts and node == list(mtree.leaves())[-2]): self.promote_subtitle(node) # - if we find in the same explicit group # a subtitle prefix before the language, # or a subtitle suffix after the language, # then upgrade the language explicit_group = mtree.node_at(node.node_idx[:2]) group_str = explicit_group.value.lower() for sub_prefix in subtitle_prefixes: if (sub_prefix in find_words(group_str) and 0 <= group_str.find(sub_prefix) < (node.span[0] - explicit_group.span[0])): prefixes.append((explicit_group, sub_prefix)) self.promote_subtitle(node) # - if a language is in an explicit group just preceded by "st", # it is a subtitle language (eg: '...st[fr-eng]...') try: idx = node.node_idx previous = list(mtree.node_at((idx[0], idx[1] - 1)).leaves())[-1] if previous.value.lower()[-2:] == 'st': self.promote_subtitle(node) except IndexError: pass for node in mtree.nodes(): if 'language' not in node.guess: continue explicit_group = mtree.node_at(node.node_idx[:2]) group_str = explicit_group.value.lower() for sub_suffix in subtitle_suffixes: if (sub_suffix in find_words(group_str) and (node.span[0] - explicit_group.span[0]) < group_str.find(sub_suffix)): is_a_prefix = False for prefix in prefixes: if prefix[0] == explicit_group and group_str.find(prefix[1]) == group_str.find(sub_suffix): is_a_prefix = True break if not is_a_prefix: self.promote_subtitle(node) guessit-0.11.0/guessit/transfo/split_path_components.py0000664000175000017500000000506612571103570024205 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from os.path import splitext from guessit.plugins.transformers import Transformer from guessit import fileutils class SplitPathComponents(Transformer): def __init__(self): Transformer.__init__(self, 255) def process(self, mtree, options=None): """first split our path into dirs + basename + ext :return: the filename split into [ dir*, basename, ext ] """ if not options.get('name_only'): components = fileutils.split_path(mtree.value) basename = components.pop(-1) components += list(splitext(basename)) components[-1] = components[-1][1:] # remove the '.' from the extension mtree.split_on_components(components, category='path') else: mtree.split_on_components([mtree.value, ''], category='path') def post_process(self, mtree, options=None): """ Decrease confidence for properties found in directories, filename should always have priority. :param mtree: :param options: :return: """ if not options.get('name_only'): path_nodes = [node for node in mtree.nodes() if node.category == 'path'] for path_node in path_nodes[:-2]: self.alter_confidence(path_node, 0.3) try: last_directory_node = path_nodes[-2] self.alter_confidence(last_directory_node, 0.6) except IndexError: pass def alter_confidence(self, node, factor): for guess in node.guesses: for k in guess.keys(): confidence = guess.confidence(k) guess.set_confidence(k, confidence * factor) guessit-0.11.0/guessit/transfo/split_explicit_groups.py0000664000175000017500000000536412571103570024225 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from functools import reduce from guessit.plugins.transformers import Transformer from guessit.textutils import find_first_level_groups from guessit.patterns import group_delimiters class SplitExplicitGroups(Transformer): def __init__(self): Transformer.__init__(self, 250) def process(self, mtree, options=None): """split each of those into explicit groups (separated by parentheses or square brackets) :return: return the string split into explicit groups, that is, those either between parenthese, square brackets or curly braces, and those separated by a dash.""" for c in mtree.unidentified_leaves(): groups = find_first_level_groups(c.value, group_delimiters[0]) for delimiters in group_delimiters: flatten = lambda l, x: l + find_first_level_groups(x, delimiters) groups = reduce(flatten, groups, []) # do not do this at this moment, it is not strong enough and can break other # patterns, such as dates, etc... # groups = functools.reduce(lambda l, x: l + x.split('-'), groups, []) c.split_on_components(groups, category='explicit') def post_process(self, mtree, options=None): """ Decrease confidence for properties found in explicit groups. :param mtree: :param options: :return: """ if not options.get('name_only'): explicit_nodes = [node for node in mtree.nodes() if node.category == 'explicit' and node.is_explicit()] for explicit_node in explicit_nodes: self.alter_confidence(explicit_node, 0.5) def alter_confidence(self, node, factor): for guess in node.guesses: for k in guess.keys(): confidence = guess.confidence(k) guess.set_confidence(k, confidence * factor) guessit-0.11.0/guessit/transfo/__init__.py0000664000175000017500000000216012410027040021305 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals class TransformerException(Exception): def __init__(self, transformer, message): # Call the base class constructor with the parameters it needs Exception.__init__(self, message) self.transformer = transformerguessit-0.11.0/guessit/transfo/guess_release_group.py0000664000175000017500000002403512571103570023630 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import re from guessit.plugins.transformers import Transformer from guessit.matcher import GuessFinder, build_guess from guessit.containers import PropertiesContainer from guessit.patterns import sep from guessit.guess import Guess from guessit.textutils import strip_brackets class GuessReleaseGroup(Transformer): def __init__(self): Transformer.__init__(self, -190) self.container = PropertiesContainer(canonical_from_pattern=False) self._allowed_groupname_pattern = '[\w@#€£$&!\?]' self._forbidden_groupname_lambda = [lambda elt: elt in ['rip', 'by', 'for', 'par', 'pour', 'bonus'], lambda elt: self._is_number(elt)] # If the previous property in this list, the match will be considered as safe # and group name can contain a separator. self.previous_safe_properties = ['videoCodec', 'format', 'videoApi', 'audioCodec', 'audioProfile', 'videoProfile', 'audioChannels', 'screenSize', 'other'] self.previous_safe_values = {'other': ['Complete']} self.next_safe_properties = ['extension', 'website'] self.next_safe_values = {'format': ['Telesync']} self.next_unsafe_properties = list(self.previous_safe_properties) self.next_unsafe_properties.extend(['episodeNumber', 'season']) self.container.sep_replace_char = '-' self.container.canonical_from_pattern = False self.container.enhance = True self.container.register_property('releaseGroup', self._allowed_groupname_pattern + '+') self.container.register_property('releaseGroup', self._allowed_groupname_pattern + '+-' + self._allowed_groupname_pattern + '+') self.re_sep = re.compile('(' + sep + ')') def register_arguments(self, opts, naming_opts, output_opts, information_opts, webservice_opts, other_options): naming_opts.add_argument('-G', '--expected-group', action='append', dest='expected_group', help='Expected release group (can be used multiple times)') def supported_properties(self): return self.container.get_supported_properties() @staticmethod def _is_number(s): try: int(s) return True except ValueError: return False def validate_group_name(self, guess): val = guess['releaseGroup'] if len(val) > 1: checked_val = "" forbidden = False for elt in self.re_sep.split(val): # separators are in the list because of capturing group if forbidden: # Previous was forbidden, don't had separator forbidden = False continue for forbidden_lambda in self._forbidden_groupname_lambda: forbidden = forbidden_lambda(elt.lower()) if forbidden: if checked_val: # Removing previous separator checked_val = checked_val[0:len(checked_val) - 1] break if not forbidden: checked_val += elt val = checked_val if not val: return False if self.re_sep.match(val[-1]): val = val[:len(val)-1] if not val: return False if self.re_sep.match(val[0]): val = val[1:] if not val: return False guess['releaseGroup'] = val forbidden = False for forbidden_lambda in self._forbidden_groupname_lambda: forbidden = forbidden_lambda(val.lower()) if forbidden: break if not forbidden: return True return False @staticmethod def is_leaf_previous(leaf, node): if leaf.span[1] <= node.span[0]: for idx in range(leaf.span[1], node.span[0]): if leaf.root.value[idx] not in sep: return False return True return False def validate_next_leaves(self, node): if 'series' in node.root.info or 'title' in node.root.info: # --expected-series or --expected-title is used. return True next_leaf = node.root.next_leaf(node) node_idx = node.node_last_idx while next_leaf and next_leaf.node_last_idx >= node_idx: node_idx = next_leaf.node_last_idx # Check next properties in the same group are not in unsafe properties list for next_unsafe_property in self.next_unsafe_properties: if next_unsafe_property in next_leaf.info: return False next_leaf = next_leaf.root.next_leaf(next_leaf) # Make sure to avoid collision with 'series' or 'title' guessed later. Should be more precise. leaves = node.root.unidentified_leaves() return len(list(leaves)) > 1 def validate_node(self, leaf, node, safe=False): if not self.is_leaf_previous(leaf, node): return False if not self.validate_next_leaves(node): return False if safe: for k, v in leaf.guess.items(): if k in self.previous_safe_values and v not in self.previous_safe_values[k]: return False return True def guess_release_group(self, string, node=None, options=None): if options and options.get('expected_group'): expected_container = PropertiesContainer(enhance=True, canonical_from_pattern=False) for expected_group in options.get('expected_group'): if expected_group.startswith('re:'): expected_group = expected_group[3:] expected_group = expected_group.replace(' ', '-') expected_container.register_property('releaseGroup', expected_group, enhance=True) else: expected_group = re.escape(expected_group) expected_container.register_property('releaseGroup', expected_group, enhance=False) found = expected_container.find_properties(string, node, options, 'releaseGroup') guess = expected_container.as_guess(found, string, self.validate_group_name) if guess: return guess found = self.container.find_properties(string, node, options, 'releaseGroup') guess = self.container.as_guess(found, string, self.validate_group_name) validated_guess = None if guess: group_node = node.group_node() if group_node: for leaf in group_node.leaves_containing(self.previous_safe_properties): if self.validate_node(leaf, node, True): if leaf.root.value[leaf.span[1]] == '-': guess.metadata().confidence = 1 else: guess.metadata().confidence = 0.7 validated_guess = guess if not validated_guess: # If previous group last leaf is identified as a safe property, # consider the raw value as a releaseGroup previous_group_node = node.previous_group_node() if previous_group_node: for leaf in previous_group_node.leaves_containing(self.previous_safe_properties): if self.validate_node(leaf, node, False): guess = Guess({'releaseGroup': node.value}, confidence=1, input=node.value, span=(0, len(node.value))) if self.validate_group_name(guess): node.guess = guess validated_guess = guess if validated_guess: # If following group nodes have only one unidentified leaf, it belongs to the release group next_group_node = node while True: next_group_node = next_group_node.next_group_node() if next_group_node: leaves = list(next_group_node.leaves()) if len(leaves) == 1 and not leaves[0].guess: validated_guess['releaseGroup'] = validated_guess['releaseGroup'] + leaves[0].value leaves[0].guess = validated_guess else: break else: break if not validated_guess and node.is_explicit() and node.node_last_idx == 0: # first node from group validated_guess = build_guess(node, 'releaseGroup', value=node.value[1:len(node.value)-1]) validated_guess.metadata().confidence = 0.4 validated_guess.metadata().span = 1, len(node.value) node.guess = validated_guess if validated_guess: # Strip brackets validated_guess['releaseGroup'] = strip_brackets(validated_guess['releaseGroup']) return validated_guess def process(self, mtree, options=None): GuessFinder(self.guess_release_group, None, self.log, options).process_nodes(mtree.unidentified_leaves()) guessit-0.11.0/guessit/transfo/guess_weak_episodes_rexps.py0000664000175000017500000001407012571103570025035 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import re from guessit.patterns.list import list_parser, all_separators_re from guessit.plugins.transformers import Transformer from guessit.matcher import GuessFinder from guessit.patterns import sep, build_or_pattern from guessit.containers import PropertiesContainer from guessit.patterns.numeral import numeral, parse_numeral from guessit.date import valid_year class GuessWeakEpisodesRexps(Transformer): def __init__(self): Transformer.__init__(self, 15) of_separators = ['of', 'sur', '/', '\\'] of_separators_re = re.compile(build_or_pattern(of_separators, escape=True), re.IGNORECASE) self.container = PropertiesContainer(enhance=False, canonical_from_pattern=False, remove_duplicates=True) episode_words = ['episodes?'] def episode_list_parser(value): return list_parser(value, 'episodeList') def season_episode_parser(episode_number): epnum = parse_numeral(episode_number) if not valid_year(epnum): if epnum > 100: season, epnum = epnum // 100, epnum % 100 # episodes which have a season > 50 are most likely errors # (Simpson is at 25!) if season > 50: return None return {'season': season, 'episodeNumber': epnum} else: return epnum self.container.register_property(['episodeNumber', 'season'], '[0-9]{2,4}', confidence=0.6, formatter=season_episode_parser, disabler=lambda options: options.get('episode_prefer_number') if options else False) self.container.register_property(['episodeNumber', 'season'], '[0-9]{4}', confidence=0.6, formatter=season_episode_parser) self.container.register_property(None, '(' + build_or_pattern(episode_words) + sep + '?(?P' + numeral + '))[^0-9]', confidence=0.4, formatter=parse_numeral) self.container.register_property(None, r'(?P' + numeral + ')' + sep + '?' + of_separators_re.pattern + sep + '?(?P' + numeral +')', confidence=0.6, formatter=parse_numeral) self.container.register_property('episodeNumber', '[^0-9](\d{2,3}' + '(?:' + sep + '?' + all_separators_re.pattern + sep + '?' + '\d{2,3}' + ')*)', confidence=0.4, formatter=episode_list_parser, disabler=lambda options: not options.get('episode_prefer_number') if options else True) self.container.register_property('episodeNumber', r'^' + sep + '?(\d{2,3}' + '(?:' + sep + '?' + all_separators_re.pattern + sep + '?' + '\d{2,3}' + ')*)' + sep, confidence=0.4, formatter=episode_list_parser, disabler=lambda options: not options.get('episode_prefer_number') if options else True) self.container.register_property('episodeNumber', sep + r'(\d{2,3}' + '(?:' + sep + '?' + all_separators_re.pattern + sep + '?' + '\d{2,3}' + ')*)' + sep + '?$', confidence=0.4, formatter=episode_list_parser, disabler=lambda options: not options.get('episode_prefer_number') if options else True) def supported_properties(self): return self.container.get_supported_properties() def guess_weak_episodes_rexps(self, string, node=None, options=None): properties = self.container.find_properties(string, node, options) guess = self.container.as_guess(properties, string) if node and guess: if 'episodeNumber' in guess and 'season' in guess: existing_guesses = list(filter(lambda x: 'season' in x and 'episodeNumber' in x, node.group_node().guesses)) if existing_guesses: return None elif 'episodeNumber' in guess: # If we only have episodeNumber in the guess, and another node contains both season and episodeNumber # keep only the second. safe_guesses = list(filter(lambda x: 'season' in x and 'episodeNumber' in x, node.group_node().guesses)) if safe_guesses: return None else: # If we have other nodes containing episodeNumber, create an episodeList. existing_guesses = list(filter(lambda x: 'season' not in x and 'episodeNumber' in x, node.group_node().guesses)) for existing_guess in existing_guesses: if 'episodeList' not in existing_guess: existing_guess['episodeList'] = [existing_guess['episodeNumber']] existing_guess['episodeList'].append(guess['episodeNumber']) existing_guess['episodeList'].sort() if existing_guess['episodeNumber'] > guess['episodeNumber']: existing_guess.set_confidence('episodeNumber', 0) else: guess.set_confidence('episodeNumber', 0) guess['episodeList'] = list(existing_guess['episodeList']) return guess def should_process(self, mtree, options=None): return mtree.guess.get('type', '').startswith('episode') def process(self, mtree, options=None): GuessFinder(self.guess_weak_episodes_rexps, 0.6, self.log, options).process_nodes(mtree.unidentified_leaves()) guessit-0.11.0/guessit/transfo/guess_episode_details.py0000664000175000017500000000537212534564763024152 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import itertools from guessit.plugins.transformers import Transformer from guessit.matcher import found_guess from guessit.containers import PropertiesContainer class GuessEpisodeDetails(Transformer): def __init__(self): Transformer.__init__(self, -205) self.container = PropertiesContainer() self.container.register_property('episodeDetails', 'Special', 'Bonus', 'Omake', 'Ova', 'Oav', 'Pilot', 'Unaired') self.container.register_property('episodeDetails', 'Extras?', canonical_form='Extras') def guess_details(self, string, node=None, options=None): properties = self.container.find_properties(string, node, options, 'episodeDetails', multiple=True) guesses = self.container.as_guess(properties, multiple=True) return guesses def second_pass_options(self, mtree, options=None): if not mtree.guess.get('type', '').startswith('episode'): for unidentified_leaf in mtree.unidentified_leaves(): properties = self.container.find_properties(unidentified_leaf.value, unidentified_leaf, options, 'episodeDetails') guess = self.container.as_guess(properties) if guess: return {'type': 'episode'} return None def supported_properties(self): return self.container.get_supported_properties() def process(self, mtree, options=None): if (mtree.guess.get('type', '').startswith('episode') and (not mtree.info.get('episodeNumber') or mtree.info.get('season') == 0)): for leaf in itertools.chain(mtree.leaves_containing('title'), mtree.unidentified_leaves()): guesses = self.guess_details(leaf.value, leaf, options) for guess in guesses: found_guess(leaf, guess, update_guess=False) return None guessit-0.11.0/guessit/transfo/guess_episodes_rexps.py0000664000175000017500000002777512571103570024046 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import re from guessit.patterns.list import list_parser, all_separators_re from guessit.plugins.transformers import Transformer from guessit.matcher import GuessFinder from guessit.patterns import sep, build_or_pattern from guessit.containers import PropertiesContainer, WeakValidator, NoValidator, ChainedValidator, DefaultValidator, \ FormatterValidator from guessit.patterns.numeral import numeral, digital_numeral, parse_numeral class GuessEpisodesRexps(Transformer): def __init__(self): Transformer.__init__(self, 20) of_separators = ['of', 'sur', '/', '\\'] of_separators_re = re.compile(build_or_pattern(of_separators, escape=True), re.IGNORECASE) season_words = ['seasons?', 'saisons?', 'series?'] episode_words = ['episodes?'] season_markers = ['s'] episode_markers = ['e', 'ep'] self.container = PropertiesContainer(enhance=False, canonical_from_pattern=False) season_words_re = re.compile(build_or_pattern(season_words), re.IGNORECASE) episode_words_re = re.compile(build_or_pattern(episode_words), re.IGNORECASE) season_markers_re = re.compile(build_or_pattern(season_markers), re.IGNORECASE) episode_markers_re = re.compile(build_or_pattern(episode_markers), re.IGNORECASE) def episode_parser_x(value): return list_parser(value, 'episodeList', discrete_separators_re=re.compile('x', re.IGNORECASE)) def episode_parser_e(value): return list_parser(value, 'episodeList', discrete_separators_re=re.compile('e', re.IGNORECASE), fill_gaps=True) def episode_parser(value): return list_parser(value, 'episodeList') def season_parser(value): return list_parser(value, 'seasonList') class ResolutionCollisionValidator(object): @staticmethod def validate(prop, string, node, match, entry_start, entry_end): # Invalidate when season or episode is more than 100. try: season_value = season_parser(match.group(2)) episode_value = episode_parser_x(match.group(3)) return season_value < 100 or episode_value < 100 except: # This may occur for 1xAll or patterns like this. return True self.container.register_property(None, r'(' + season_words_re.pattern + sep + '?(?P' + numeral + ')' + sep + '?' + season_words_re.pattern + '?)', confidence=1.0, formatter=parse_numeral) self.container.register_property(None, r'(' + season_words_re.pattern + sep + '?(?P' + digital_numeral + '(?:' + sep + '?' + all_separators_re.pattern + sep + '?' + digital_numeral + ')*)' + sep + '?' + season_words_re.pattern + '?)' + sep, confidence=1.0, formatter={None: parse_numeral, 'season': season_parser}, validator=ChainedValidator(DefaultValidator(), FormatterValidator('season', lambda x: len(x) > 1 if hasattr(x, '__len__') else False))) self.container.register_property(None, r'(' + season_markers_re.pattern + '(?P' + digital_numeral + ')[^0-9]?' + sep + '?(?P(?:e' + digital_numeral + '(?:' + sep + '?[e-]' + digital_numeral + ')*)))', confidence=1.0, formatter={None: parse_numeral, 'episodeNumber': episode_parser_e, 'season': season_parser}, validator=NoValidator()) self.container.register_property(None, r'(' + season_markers_re.pattern + '(?P' + digital_numeral + ')[^0-9]?' + sep + '?(?P(?:e' + digital_numeral + '(?:' + sep + '?' + all_separators_re.pattern + sep + '?' + digital_numeral + ')*)))', confidence=1.0, formatter={None: parse_numeral, 'episodeNumber': episode_parser, 'season': season_parser}, validator=NoValidator()) self.container.register_property(None, sep + r'((?P' + digital_numeral + ')' + sep + '' + '(?P(?:x' + sep + digital_numeral + '(?:' + sep + '[x-]' + digital_numeral + ')*)))', confidence=1.0, formatter={None: parse_numeral, 'episodeNumber': episode_parser_x, 'season': season_parser}, validator=ChainedValidator(DefaultValidator(), ResolutionCollisionValidator())) self.container.register_property(None, r'((?P' + digital_numeral + ')' + '(?P(?:x' + digital_numeral + '(?:[x-]' + digital_numeral + ')*)))', confidence=1.0, formatter={None: parse_numeral, 'episodeNumber': episode_parser_x, 'season': season_parser}, validator=ChainedValidator(DefaultValidator(), ResolutionCollisionValidator())) self.container.register_property(None, r'(' + season_markers_re.pattern + '(?P' + digital_numeral + '(?:' + sep + '?' + all_separators_re.pattern + sep + '?' + digital_numeral + ')*))', confidence=0.6, formatter={None: parse_numeral, 'season': season_parser}, validator=NoValidator()) self.container.register_property(None, r'((?P' + digital_numeral + ')' + sep + '?v(?P\d+))', confidence=0.6, formatter=parse_numeral) self.container.register_property('version', sep + r'(V\d+)' + sep, confidence=0.6, formatter=parse_numeral, validator=NoValidator()) self.container.register_property(None, r'(ep' + sep + r'?(?P' + digital_numeral + ')' + sep + '?)', confidence=0.7, formatter=parse_numeral) self.container.register_property(None, r'(ep' + sep + r'?(?P' + digital_numeral + ')' + sep + '?v(?P\d+))', confidence=0.7, formatter=parse_numeral) self.container.register_property(None, r'(' + episode_markers_re.pattern + '(?P' + digital_numeral + '(?:' + sep + '?' + all_separators_re.pattern + sep + '?' + digital_numeral + ')*))', confidence=0.6, formatter={None: parse_numeral, 'episodeNumber': episode_parser}) self.container.register_property(None, r'(' + episode_words_re.pattern + sep + '?(?P' + digital_numeral + '(?:' + sep + '?' + all_separators_re.pattern + sep + '?' + digital_numeral + ')*)' + sep + '?' + episode_words_re.pattern + '?)', confidence=0.8, formatter={None: parse_numeral, 'episodeNumber': episode_parser}) self.container.register_property(None, r'(' + episode_markers_re.pattern + '(?P' + digital_numeral + ')' + sep + '?v(?P\d+))', confidence=0.6, formatter={None: parse_numeral, 'episodeNumber': episode_parser}) self.container.register_property(None, r'(' + episode_words_re.pattern + sep + '?(?P' + digital_numeral + ')' + sep + '?v(?P\d+))', confidence=0.8, formatter={None: parse_numeral, 'episodeNumber': episode_parser}) self.container.register_property('episodeNumber', r'^' + sep + '+(\d{2}' + '(?:' + sep + '?' + all_separators_re.pattern + sep + '?' + '\d{2}' + ')*)' + sep, confidence=0.4, formatter=episode_parser) self.container.register_property('episodeNumber', r'^' + sep + '+0(\d{1,2}' + '(?:' + sep + '?' + all_separators_re.pattern + sep + '?' + '0\d{1,2}' + ')*)' + sep, confidence=0.4, formatter=episode_parser) self.container.register_property('episodeNumber', sep + r'(\d{2}' + '(?:' + sep + '?' + all_separators_re.pattern + sep + '?' + r'\d{2}' + ')*)' + sep + '+$', confidence=0.4, formatter=episode_parser) self.container.register_property('episodeNumber', sep + r'0(\d{1,2}' + '(?:' + sep + '?' + all_separators_re.pattern + sep + '?' + r'0\d{1,2}' + ')*)' + sep + '+$', confidence=0.4, formatter=episode_parser) self.container.register_property(None, r'((?P' + numeral + ')' + sep + '?' + of_separators_re.pattern + sep + '?(?P' + numeral + ')(?:' + sep + '?(?:episodes?|eps?))?)', confidence=0.7, formatter=parse_numeral) self.container.register_property(None, r'((?:episodes?|eps?)' + sep + '?(?P' + numeral + ')' + sep + '?' + of_separators_re.pattern + sep + '?(?P' + numeral + '))', confidence=0.7, formatter=parse_numeral) self.container.register_property(None, r'((?:seasons?|saisons?|s)' + sep + '?(?P' + numeral + ')' + sep + '?' + of_separators_re.pattern + sep + '?(?P' + numeral + '))', confidence=0.7, formatter=parse_numeral) self.container.register_property(None, r'((?P' + numeral + ')' + sep + '?' + of_separators_re.pattern + sep + '?(?P' + numeral + ')' + sep + '?(?:seasons?|saisons?|s))', confidence=0.7, formatter=parse_numeral) self.container.register_canonical_properties('other', 'FiNAL', 'Complete', validator=WeakValidator()) self.container.register_property(None, r'[^0-9]((?P' + digital_numeral + ')[^0-9 .-]?-?(?PxAll))', confidence=1.0, formatter={None: parse_numeral, 'other': lambda x: 'Complete', 'season': season_parser}, validator=ChainedValidator(DefaultValidator(), ResolutionCollisionValidator())) def register_arguments(self, opts, naming_opts, output_opts, information_opts, webservice_opts, other_options): naming_opts.add_argument('-E', '--episode-prefer-number', action='store_true', dest='episode_prefer_number', default=False, help='Guess "serie.213.avi" as the episodeNumber 213. Without this option, ' 'it will be guessed as season 2, episodeNumber 13') def supported_properties(self): return ['episodeNumber', 'season', 'episodeList', 'seasonList', 'episodeCount', 'seasonCount', 'version', 'other'] def guess_episodes_rexps(self, string, node=None, options=None): found = self.container.find_properties(string, node, options) guess = self.container.as_guess(found, string) if guess and node: if 'season' in guess and 'episodeNumber' in guess: # If two guesses contains both season and episodeNumber in same group, create an episodeList for existing_guess in node.group_node().guesses: if 'season' in existing_guess and 'episodeNumber' in existing_guess: if 'episodeList' not in existing_guess: existing_guess['episodeList'] = [existing_guess['episodeNumber']] existing_guess['episodeList'].append(guess['episodeNumber']) existing_guess['episodeList'].sort() if existing_guess['episodeNumber'] > guess['episodeNumber']: existing_guess.set_confidence('episodeNumber', 0) else: guess.set_confidence('episodeNumber', 0) guess['episodeList'] = list(existing_guess['episodeList']) elif 'episodeNumber' in guess: # If two guesses contains only episodeNumber in same group, remove the existing one. for existing_guess in node.group_node().guesses: if 'episodeNumber' in existing_guess: for k, v in existing_guess.items(): if k in guess: del guess[k] return guess def should_process(self, mtree, options=None): return mtree.guess.get('type', '').startswith('episode') def process(self, mtree, options=None): GuessFinder(self.guess_episodes_rexps, None, self.log, options).process_nodes(mtree.unidentified_leaves()) guessit-0.11.0/guessit/transfo/guess_bonus_features.py0000664000175000017500000000521312507715026024021 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.plugins.transformers import Transformer from guessit.matcher import found_property class GuessBonusFeatures(Transformer): def __init__(self): Transformer.__init__(self, -150) def supported_properties(self): return ['bonusNumber', 'bonusTitle', 'filmNumber', 'filmSeries', 'title', 'series'] def process(self, mtree, options=None): def previous_group(g): for leaf in reversed(list(mtree.unidentified_leaves())): if leaf.node_idx < g.node_idx: return leaf def next_group(g): for leaf in mtree.unidentified_leaves(): if leaf.node_idx > g.node_idx: return leaf def same_group(g1, g2): return g1.node_idx[:2] == g2.node_idx[:2] bonus = [node for node in mtree.leaves() if 'bonusNumber' in node.guess] if bonus: bonus_title = next_group(bonus[0]) if bonus_title and same_group(bonus_title, bonus[0]): found_property(bonus_title, 'bonusTitle', confidence=0.8) film_number = [node for node in mtree.leaves() if 'filmNumber' in node.guess] if film_number: film_series = previous_group(film_number[0]) if film_series: found_property(film_series, 'filmSeries', confidence=0.9) title = next_group(film_number[0]) if title: found_property(title, 'title', confidence=0.9) season = [node for node in mtree.leaves() if 'season' in node.guess] if season and 'bonusNumber' in mtree.info: series = previous_group(season[0]) if same_group(series, season[0]): found_property(series, 'series', confidence=0.9) guessit-0.11.0/guessit/transfo/guess_website.py0000664000175000017500000000537712534564763022464 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Rémi Alvergnat # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from pkg_resources import resource_stream # @UnresolvedImport from guessit.patterns import build_or_pattern from guessit.containers import PropertiesContainer from guessit.plugins.transformers import Transformer from guessit.matcher import GuessFinder TLDS = [l.strip().decode('utf-8') for l in resource_stream('guessit', 'tlds-alpha-by-domain.txt').readlines() if b'--' not in l][1:] class GuessWebsite(Transformer): def __init__(self): Transformer.__init__(self, 45) self.container = PropertiesContainer(enhance=False, canonical_from_pattern=False) tlds_pattern = build_or_pattern(TLDS) # All registered domain extension safe_tlds_pattern = build_or_pattern(['com', 'org', 'net']) # For sure a website extension safe_subdomains_pattern = build_or_pattern(['www']) # For sure a website subdomain safe_prefix_tlds_pattern = build_or_pattern(['co', 'com', 'org', 'net']) # Those words before a tlds are sure self.container.register_property('website', '(?:' + safe_subdomains_pattern + '\.)+' + r'(?:[a-z-]+\.)+' + r'(?:' + tlds_pattern + r')+') self.container.register_property('website', '(?:' + safe_subdomains_pattern + '\.)*' + r'[a-z-]+\.' + r'(?:' + safe_tlds_pattern + r')+') self.container.register_property('website', '(?:' + safe_subdomains_pattern + '\.)*' + r'[a-z-]+\.' + r'(?:' + safe_prefix_tlds_pattern + r'\.)+' + r'(?:' + tlds_pattern + r')+') def supported_properties(self): return self.container.get_supported_properties() def guess_website(self, string, node=None, options=None): found = self.container.find_properties(string, node, options, 'website') return self.container.as_guess(found, string) def process(self, mtree, options=None): GuessFinder(self.guess_website, 1.0, self.log, options).process_nodes(mtree.unidentified_leaves()) guessit-0.11.0/guessit/transfo/guess_movie_title_from_position.py0000664000175000017500000002060312572400023026252 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.plugins.transformers import Transformer from guessit.matcher import found_property from guessit import u from guessit.patterns.list import all_separators from guessit.language import all_lang_prefixes_suffixes class GuessMovieTitleFromPosition(Transformer): def __init__(self): Transformer.__init__(self, -200) def supported_properties(self): return ['title'] def should_process(self, mtree, options=None): options = options or {} return not options.get('skip_title') and not mtree.guess.get('type', '').startswith('episode') @staticmethod def excluded_word(*values): for value in values: if value.clean_value.lower() in all_separators + all_lang_prefixes_suffixes: return True return False def process(self, mtree, options=None): """ try to identify the remaining unknown groups by looking at their position relative to other known elements """ if 'title' in mtree.info: return path_nodes = list(filter(lambda x: x.category == 'path', mtree.nodes())) basename = path_nodes[-2] all_valid = lambda leaf: len(leaf.clean_value) > 0 basename_leftover = list(basename.unidentified_leaves(valid=all_valid)) try: folder = path_nodes[-3] folder_leftover = list(folder.unidentified_leaves()) except IndexError: folder = None folder_leftover = [] self.log.debug('folder: %s' % u(folder_leftover)) self.log.debug('basename: %s' % u(basename_leftover)) # specific cases: # if we find the same group both in the folder name and the filename, # it's a good candidate for title if (folder_leftover and basename_leftover and folder_leftover[0].clean_value == basename_leftover[0].clean_value and not GuessMovieTitleFromPosition.excluded_word(folder_leftover[0])): found_property(folder_leftover[0], 'title', confidence=0.8) return # specific cases: # if the basename contains a number first followed by an unidentified # group, and the folder only contains 1 unidentified one, then we have # a series # ex: Millenium Trilogy (2009)/(1)The Girl With The Dragon Tattoo(2009).mkv if len(folder_leftover) > 0 and len(basename_leftover) > 1: series = folder_leftover[0] film_number = basename_leftover[0] title = basename_leftover[1] basename_leaves = list(basename.leaves()) num = None try: num = int(film_number.clean_value) except ValueError: pass if num: self.log.debug('series: %s' % series.clean_value) self.log.debug('title: %s' % title.clean_value) if (series.clean_value != title.clean_value and series.clean_value != film_number.clean_value and basename_leaves.index(film_number) == 0 and basename_leaves.index(title) == 1 and not GuessMovieTitleFromPosition.excluded_word(title, series)): found_property(title, 'title', confidence=0.6) found_property(series, 'filmSeries', confidence=0.6) found_property(film_number, 'filmNumber', num, confidence=0.6) return if folder: year_group = folder.first_leaf_containing('year') if year_group: groups_before = folder.previous_unidentified_leaves(year_group) if groups_before: try: node = next(groups_before) if not GuessMovieTitleFromPosition.excluded_word(node): found_property(node, 'title', confidence=0.8) return except StopIteration: pass # if we have either format or videoCodec in the folder containing the # file or one of its parents, then we should probably look for the title # in there rather than in the basename try: props = list(mtree.previous_leaves_containing(mtree.children[-2], ['videoCodec', 'format', 'language'])) except IndexError: props = [] if props: group_idx = props[0].node_idx[0] if all(g.node_idx[0] == group_idx for g in props): # if they're all in the same group, take leftover info from there leftover = mtree.node_at((group_idx,)).unidentified_leaves() try: node = next(leftover) if not GuessMovieTitleFromPosition.excluded_word(node): found_property(node, 'title', confidence=0.7) return except StopIteration: pass # look for title in basename if there are some remaining unidentified # groups there if basename_leftover: # if basename is only one word and the containing folder has at least # 3 words in it, we should take the title from the folder name # ex: Movies/Alice in Wonderland DVDRip.XviD-DiAMOND/dmd-aw.avi # ex: Movies/Somewhere.2010.DVDRip.XviD-iLG/i-smwhr.avi <-- TODO: gets caught here? if (basename_leftover[0].clean_value.count(' ') == 0 and folder_leftover and folder_leftover[0].clean_value.count(' ') >= 2 and not GuessMovieTitleFromPosition.excluded_word(folder_leftover[0])): found_property(folder_leftover[0], 'title', confidence=0.7) return # if there are only many unidentified groups, take the first of which is # not inside brackets or parentheses. # ex: Movies/[阿维达].Avida.2006.FRENCH.DVDRiP.XViD-PROD.avi if basename_leftover[0].is_explicit(): for basename_leftover_elt in basename_leftover: if not basename_leftover_elt.is_explicit() and not GuessMovieTitleFromPosition.excluded_word(basename_leftover_elt): found_property(basename_leftover_elt, 'title', confidence=0.8) return # if all else fails, take the first remaining unidentified group in the # basename as title if not GuessMovieTitleFromPosition.excluded_word(basename_leftover[0]): found_property(basename_leftover[0], 'title', confidence=0.6) return # if there are no leftover groups in the basename, look in the folder name if folder_leftover and not GuessMovieTitleFromPosition.excluded_word(folder_leftover[0]): found_property(folder_leftover[0], 'title', confidence=0.5) return # if nothing worked, look if we have a very small group at the beginning # of the basename basename_leftover = basename.unidentified_leaves(valid=lambda leaf: True) try: node = next(basename_leftover) if not GuessMovieTitleFromPosition.excluded_word(node): found_property(node, 'title', confidence=0.4) return except StopIteration: pass guessit-0.11.0/guessit/transfo/guess_date.py0000664000175000017500000000446312571103570021714 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.containers import DefaultValidator from guessit.plugins.transformers import Transformer from guessit.matcher import GuessFinder from guessit.date import search_date class GuessDate(Transformer): def __init__(self): Transformer.__init__(self, 50) def register_arguments(self, opts, naming_opts, output_opts, information_opts, webservice_opts, other_options): naming_opts.add_argument('-Y', '--date-year-first', action='store_true', dest='date_year_first', default=None, help='If short date is found, consider the first digits as the year.') naming_opts.add_argument('-D', '--date-day-first', action='store_true', dest='date_day_first', default=None, help='If short date is found, consider the second digits as the day.') def supported_properties(self): return ['date'] @staticmethod def guess_date(string, node=None, options=None): date, span = search_date(string, options.get('date_year_first') if options else False, options.get('date_day_first') if options else False) if date and span and DefaultValidator.validate_string(string, span): # ensure we have a separator before and after date return {'date': date}, span return None, None def process(self, mtree, options=None): GuessFinder(self.guess_date, 1.0, self.log, options).process_nodes(mtree.unidentified_leaves()) guessit-0.11.0/guessit/transfo/split_on_dash.py0000664000175000017500000000321512571103570022411 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import re from guessit.plugins.transformers import Transformer from guessit.patterns import sep class SplitOnDash(Transformer): def __init__(self): Transformer.__init__(self, 245) def process(self, mtree, options=None): """split into '-' separated subgroups (with required separator chars around the dash) """ for node in mtree.unidentified_leaves(): indices = [] pattern = re.compile(sep + '-' + sep) match = pattern.search(node.value) while match: span = match.span() indices.extend([span[0], span[1]]) match = pattern.search(node.value, span[1]) if indices: node.partition(indices, category='dash') guessit-0.11.0/guessit/transfo/guess_video_rexps.py0000664000175000017500000000601212507717177023333 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, \ unicode_literals from guessit.patterns import _psep from guessit.containers import PropertiesContainer from guessit.plugins.transformers import Transformer from guessit.matcher import GuessFinder from guessit.patterns.numeral import parse_numeral class GuessVideoRexps(Transformer): def __init__(self): Transformer.__init__(self, 25) self.container = PropertiesContainer(canonical_from_pattern=False) self.container.register_property(None, 'cd' + _psep + '(?P[0-9])(?:' + _psep + 'of' + _psep + '(?P[0-9]))?', confidence=1.0, enhance=False, global_span=True, formatter=parse_numeral) self.container.register_property('cdNumberTotal', '([1-9])' + _psep + 'cds?', confidence=0.9, enhance=False, formatter=parse_numeral) self.container.register_property('bonusNumber', 'x([0-9]{1,2})', enhance=False, global_span=True, formatter=parse_numeral) self.container.register_property('filmNumber', 'f([0-9]{1,2})', enhance=False, global_span=True, formatter=parse_numeral) self.container.register_property('edition', 'collector', 'collector-edition', 'edition-collector', canonical_form='Collector Edition') self.container.register_property('edition', 'special-edition', 'edition-special', canonical_form='Special Edition') self.container.register_property('edition', 'criterion', 'criterion-edition', 'edition-criterion', canonical_form='Criterion Edition') self.container.register_property('edition', 'deluxe', 'cdeluxe-edition', 'edition-deluxe', canonical_form='Deluxe Edition') self.container.register_property('edition', 'director\'?s?-cut', 'director\'?s?-cut-edition', 'edition-director\'?s?-cut', canonical_form='Director\'s cut') def supported_properties(self): return self.container.get_supported_properties() def guess_video_rexps(self, string, node=None, options=None): found = self.container.find_properties(string, node, options) return self.container.as_guess(found, string) def process(self, mtree, options=None): GuessFinder(self.guess_video_rexps, None, self.log, options).process_nodes(mtree.unidentified_leaves()) guessit-0.11.0/guessit/transfo/guess_country.py0000664000175000017500000001165712571241515022507 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import logging from guessit.plugins.transformers import Transformer from babelfish import Country from guessit import Guess from guessit.textutils import iter_words from guessit.matcher import GuessFinder, found_guess from guessit.language import LNG_COMMON_WORDS import babelfish log = logging.getLogger(__name__) class GuessCountry(Transformer): def __init__(self): Transformer.__init__(self, -170) self.replace_language = frozenset(['uk']) def register_arguments(self, opts, naming_opts, output_opts, information_opts, webservice_opts, other_options): naming_opts.add_argument('-C', '--allowed-country', action='append', dest='allowed_countries', help='Allowed country (can be used multiple times)') def supported_properties(self): return ['country'] def should_process(self, mtree, options=None): options = options or {} return options.get('country', True) @staticmethod def _scan_country(country, strict=False): """ Find a country if it is at the start or end of country string """ words_match = list(iter_words(country.lower())) s = "" start = None for word_match in words_match: if not start: start = word_match.start(0) s += word_match.group(0) try: return Country.fromguessit(s), (start, word_match.end(0)) except babelfish.Error: continue words_match.reverse() s = "" end = None for word_match in words_match: if not end: end = word_match.end(0) s = word_match.group(0) + s try: return Country.fromguessit(s), (word_match.start(0), end) except babelfish.Error: continue return Country.fromguessit(country), (start, end) @staticmethod def is_valid_country(country, options=None): if options and options.get('allowed_countries'): allowed_countries = options.get('allowed_countries') return country.name.lower() in allowed_countries or country.alpha2.lower() in allowed_countries else: return (country.name.lower() not in LNG_COMMON_WORDS and country.alpha2.lower() not in LNG_COMMON_WORDS) def guess_country(self, string, node=None, options=None): c = string.strip().lower() if c not in LNG_COMMON_WORDS: try: country, country_span = self._scan_country(c, True) if self.is_valid_country(country, options): guess = Guess(country=country, confidence=1.0, input=node.value, span=(country_span[0] + 1, country_span[1] + 1)) return guess except babelfish.Error: pass return None, None def process(self, mtree, options=None): GuessFinder(self.guess_country, None, self.log, options).process_nodes(mtree.unidentified_leaves()) for node in mtree.leaves_containing('language'): c = node.clean_value.lower() if c in self.replace_language: node.guess.set('language', None) try: country = Country.fromguessit(c) if self.is_valid_country(country, options): guess = Guess(country=country, confidence=0.9, input=node.value, span=node.span) found_guess(node, guess, logger=log) except babelfish.Error: pass def post_process(self, mtree, options=None, *args, **kwargs): # if country is in the guessed properties, make it part of the series name series_leaves = list(mtree.leaves_containing('series')) country_leaves = list(mtree.leaves_containing('country')) if series_leaves and country_leaves: country_leaf = country_leaves[0] for serie_leaf in series_leaves: serie_leaf.guess['series'] += ' (%s)' % str(country_leaf.guess['country'].guessit) guessit-0.11.0/guessit/textutils.py0000664000175000017500000002570712534564763020204 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit import s from guessit.patterns import sep import functools import unicodedata import re # string-related functions def normalize_unicode(s): return unicodedata.normalize('NFC', s) def strip_brackets(s): if not s: return s if ((s[0] == '[' and s[-1] == ']') or (s[0] == '(' and s[-1] == ')') or (s[0] == '{' and s[-1] == '}')): return s[1:-1] return s _dotted_rexp = re.compile(r'(?:\W|^)(([A-Za-z]\.){2,}[A-Za-z]\.?)') def clean_default(st): for c in sep: # do not remove certain chars if c in ['-', ',']: continue if c == '.': # we should not remove the dots for acronyms and such dotted = _dotted_rexp.search(st) if dotted: s = dotted.group(1) exclude_begin, exclude_end = dotted.span(1) st = (st[:exclude_begin].replace(c, ' ') + st[exclude_begin:exclude_end] + st[exclude_end:].replace(c, ' ')) continue st = st.replace(c, ' ') parts = st.split() result = ' '.join(p for p in parts if p != '') # now also remove dashes on the outer part of the string while result and result[0] in '-': result = result[1:] while result and result[-1] in '-': result = result[:-1] return result _words_rexp = re.compile('\w+', re.UNICODE) def find_words(s): return _words_rexp.findall(s.replace('_', ' ')) def iter_words(s): return _words_rexp.finditer(s.replace('_', ' ')) def reorder_title(title, articles=('the',), separators=(',', ', ')): ltitle = title.lower() for article in articles: for separator in separators: suffix = separator + article if ltitle[-len(suffix):] == suffix: return title[-len(suffix) + len(separator):] + ' ' + title[:-len(suffix)] return title def str_replace(string, pos, c): return string[:pos] + c + string[pos + 1:] def str_fill(string, region, c): start, end = region return string[:start] + c * (end - start) + string[end:] def levenshtein(a, b): if not a: return len(b) if not b: return len(a) m = len(a) n = len(b) d = [] for i in range(m + 1): d.append([0] * (n + 1)) for i in range(m + 1): d[i][0] = i for j in range(n + 1): d[0][j] = j for i in range(1, m + 1): for j in range(1, n + 1): if a[i - 1] == b[j - 1]: cost = 0 else: cost = 1 d[i][j] = min(d[i - 1][j] + 1, # deletion d[i][j - 1] + 1, # insertion d[i - 1][j - 1] + cost # substitution ) return d[m][n] # group-related functions def find_first_level_groups_span(string, enclosing): """Return a list of pairs (start, end) for the groups delimited by the given enclosing characters. This does not return nested groups, ie: '(ab(c)(d))' will return a single group containing the whole string. >>> find_first_level_groups_span('abcd', '()') [] >>> find_first_level_groups_span('abc(de)fgh', '()') [(3, 7)] >>> find_first_level_groups_span('(ab(c)(d))', '()') [(0, 10)] >>> find_first_level_groups_span('ab[c]de[f]gh(i)', '[]') [(2, 5), (7, 10)] """ opening, closing = enclosing depth = [] # depth is a stack of indices where we opened a group result = [] for i, c, in enumerate(string): if c == opening: depth.append(i) elif c == closing: try: start = depth.pop() end = i if not depth: # we emptied our stack, so we have a 1st level group result.append((start, end + 1)) except IndexError: # we closed a group which was not opened before pass return result def split_on_groups(string, groups): """Split the given string using the different known groups for boundaries. >>> s(split_on_groups('0123456789', [ (2, 4) ])) ['01', '23', '456789'] >>> s(split_on_groups('0123456789', [ (2, 4), (4, 6) ])) ['01', '23', '45', '6789'] >>> s(split_on_groups('0123456789', [ (5, 7), (2, 4) ])) ['01', '23', '4', '56', '789'] """ if not groups: return [string] boundaries = sorted(set(functools.reduce(lambda l, x: l + list(x), groups, []))) if boundaries[0] != 0: boundaries.insert(0, 0) if boundaries[-1] != len(string): boundaries.append(len(string)) groups = [string[start:end] for start, end in zip(boundaries[:-1], boundaries[1:])] return [g for g in groups if g] # return only non-empty groups def find_first_level_groups(string, enclosing, blank_sep=None): """Return a list of groups that could be split because of explicit grouping. The groups are delimited by the given enclosing characters. You can also specify if you want to blank the separator chars in the returned list of groups by specifying a character for it. None means it won't be replaced. This does not return nested groups, ie: '(ab(c)(d))' will return a single group containing the whole string. >>> s(find_first_level_groups('', '()')) [''] >>> s(find_first_level_groups('abcd', '()')) ['abcd'] >>> s(find_first_level_groups('abc(de)fgh', '()')) ['abc', '(de)', 'fgh'] >>> s(find_first_level_groups('(ab(c)(d))', '()', blank_sep = '_')) ['_ab(c)(d)_'] >>> s(find_first_level_groups('ab[c]de[f]gh(i)', '[]')) ['ab', '[c]', 'de', '[f]', 'gh(i)'] >>> s(find_first_level_groups('()[]()', '()', blank_sep = '-')) ['--', '[]', '--'] """ groups = find_first_level_groups_span(string, enclosing) if blank_sep: for start, end in groups: string = str_replace(string, start, blank_sep) string = str_replace(string, end - 1, blank_sep) return split_on_groups(string, groups) _camel_word2_set = {'is', 'to'} _camel_word3_set = {'the'} def _camel_split_and_lower(string, i): """Retrieves a tuple (need_split, need_lower) need_split is True if this char is a first letter in a camelCasedString. need_lower is True if this char should be lowercased. """ def islower(c): return c.isalpha() and not c.isupper() previous_char2 = string[i - 2] if i > 1 else None previous_char = string[i - 1] if i > 0 else None char = string[i] next_char = string[i + 1] if i + 1 < len(string) else None next_char2 = string[i + 2] if i + 2 < len(string) else None char_upper = char.isupper() char_lower = islower(char) # previous_char2_lower = islower(previous_char2) if previous_char2 else False previous_char2_upper = previous_char2.isupper() if previous_char2 else False previous_char_lower = islower(previous_char) if previous_char else False previous_char_upper = previous_char.isupper() if previous_char else False next_char_upper = next_char.isupper() if next_char else False next_char_lower = islower(next_char) if next_char else False next_char2_upper = next_char2.isupper() if next_char2 else False # next_char2_lower = islower(next_char2) if next_char2 else False mixedcase_word = (previous_char_upper and char_lower and next_char_upper) or \ (previous_char_lower and char_upper and next_char_lower and next_char2_upper) or \ (previous_char2_upper and previous_char_lower and char_upper) if mixedcase_word: word2 = (char + next_char).lower() if next_char else None word3 = (char + next_char + next_char2).lower() if next_char and next_char2 else None word2b = (previous_char2 + previous_char).lower() if previous_char2 and previous_char else None if word2 in _camel_word2_set or word2b in _camel_word2_set or word3 in _camel_word3_set: mixedcase_word = False uppercase_word = previous_char_upper and char_upper and next_char_upper or (char_upper and next_char_upper and next_char2_upper) need_split = char_upper and previous_char_lower and not mixedcase_word if not need_split: previous_char_upper = string[i - 1].isupper() if i > 0 else False next_char_lower = (string[i + 1].isalpha() and not string[i + 1].isupper()) if i + 1 < len(string) else False need_split = char_upper and previous_char_upper and next_char_lower uppercase_word = previous_char_upper and not next_char_lower need_lower = not uppercase_word and not mixedcase_word and need_split return need_split, need_lower def is_camel(string): """ >>> is_camel('dogEATDog') True >>> is_camel('DeathToCamelCase') True >>> is_camel('death_to_camel_case') False >>> is_camel('TheBest') True >>> is_camel('The Best') False """ for i in range(0, len(string)): need_split, _ = _camel_split_and_lower(string, i) if need_split: return True return False def from_camel(string): """ >>> from_camel('dogEATDog') == 'dog EAT dog' True >>> from_camel('DeathToCamelCase') == 'Death to camel case' True >>> from_camel('TheBest') == 'The best' True >>> from_camel('MiXedCaSe is not camelCase') == 'MiXedCaSe is not camel case' True """ if not string: return string pieces = [] for i in range(0, len(string)): char = string[i] need_split, need_lower = _camel_split_and_lower(string, i) if need_split: pieces.append(' ') if need_lower: pieces.append(char.lower()) else: pieces.append(char) return ''.join(pieces) def common_words(s1, s2): common = [] words1 = set(s1.split()) for word in s2.split(): # strip some chars here, e.g. as in [1] if word in words1: common.append(word) return common guessit-0.11.0/guessit/matcher.py0000664000175000017500000003076412572126610017544 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # Copyright (c) 2013 Rémi Alvergnat # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, \ unicode_literals import logging import inspect from guessit import PY3, u from guessit.transfo import TransformerException from guessit.matchtree import MatchTree from guessit.textutils import normalize_unicode, clean_default from guessit.guess import Guess log = logging.getLogger(__name__) class IterativeMatcher(object): """An iterative matcher tries to match different patterns that appear in the filename. The ``filetype`` argument indicates which type of file you want to match. If it is undefined, the matcher will try to see whether it can guess that the file corresponds to an episode, or otherwise will assume it is a movie. The recognized ``filetype`` values are: ``['subtitle', 'info', 'movie', 'moviesubtitle', 'movieinfo', 'episode', 'episodesubtitle', 'episodeinfo']`` ``options`` is a dict of options values to be passed to the transformations used by the matcher. The IterativeMatcher works mainly in 2 steps: First, it splits the filename into a match_tree, which is a tree of groups which have a semantic meaning, such as episode number, movie title, etc... The match_tree created looks like the following:: 0000000000000000000000000000000000000000000000000000000000000000000000000000000000 111 0000011111111111112222222222222233333333444444444444444455555555666777777778888888 000 0000000000000000000000000000000001111112011112222333333401123334000011233340000000 000 __________________(The.Prestige).______.[____.HP.______.{__-___}.St{__-___}.Chaps].___ xxxxxttttttttttttt ffffff vvvv xxxxxx ll lll xx xxx ccc [XCT].Le.Prestige.(The.Prestige).DVDRip.[x264.HP.He-Aac.{Fr-Eng}.St{Fr-Eng}.Chaps].mkv The first 3 lines indicates the group index in which a char in the filename is located. So for instance, ``x264`` (in the middle) is the group (0, 4, 1), and it corresponds to a video codec, denoted by the letter ``v`` in the 4th line. (for more info, see guess.matchtree.to_string) Second, it tries to merge all this information into a single object containing all the found properties, and does some (basic) conflict resolution when they arise. """ def __init__(self, filename, options=None, **kwargs): options = dict(options or {}) for k, v in kwargs.items(): if k not in options or not options[k]: options[k] = v # options dict has priority over keyword arguments self._validate_options(options) if not PY3 and not isinstance(filename, unicode): log.warning('Given filename to matcher is not unicode...') filename = filename.decode('utf-8') filename = normalize_unicode(filename) if options and options.get('clean_function'): clean_function = options.get('clean_function') if not hasattr(clean_function, '__call__'): module, function = clean_function.rsplit('.') if not module: module = 'guessit.textutils' clean_function = getattr(__import__(module), function) if not clean_function: log.error('Can\'t find clean function %s. Default will be used.' % options.get('clean_function')) clean_function = clean_default else: clean_function = clean_default self.match_tree = MatchTree(filename, clean_function=clean_function) self.options = options self._transfo_calls = [] # sanity check: make sure we don't process a (mostly) empty string if clean_function(filename).strip() == '': return from guessit.plugins import transformers try: mtree = self.match_tree if 'type' in self.options: mtree.guess.set('type', self.options['type'], confidence=0.0) # Process for transformer in transformers.all_transformers(): disabled = options.get('disabled_transformers') if not disabled or transformer.name not in disabled: self._process(transformer, False) # Post-process for transformer in transformers.all_transformers(): disabled = options.get('disabled_transformers') if not disabled or transformer.name not in disabled: self._process(transformer, True) log.debug('Found match tree:\n%s' % u(mtree)) except TransformerException as e: log.debug('An error has occurred in Transformer %s: %s' % (e.transformer, e)) def _process(self, transformer, post=False): if not hasattr(transformer, 'should_process') or transformer.should_process(self.match_tree, self.options): if post: transformer.post_process(self.match_tree, self.options) else: transformer.process(self.match_tree, self.options) self._transfo_calls.append(transformer) @property def second_pass_options(self): second_pass_options = {} for transformer in self._transfo_calls: if hasattr(transformer, 'second_pass_options'): transformer_second_pass_options = transformer.second_pass_options(self.match_tree, self.options) if transformer_second_pass_options: second_pass_options.update(transformer_second_pass_options) return second_pass_options @staticmethod def _validate_options(options): valid_filetypes = ('subtitle', 'info', 'video', 'movie', 'moviesubtitle', 'movieinfo', 'episode', 'episodesubtitle', 'episodeinfo') type_ = options.get('type') if type_ and type_ not in valid_filetypes: raise ValueError("filetype needs to be one of %s" % (valid_filetypes,)) def matched(self): return self.match_tree.matched() def build_guess(node, name, value=None, confidence=1.0): guess = Guess({name: node.clean_value if value is None else value}, confidence=confidence) guess.metadata().input = node.value if value is None else value if value is None: left_offset = 0 right_offset = 0 clean_value = node.clean_value if clean_value: for i in range(0, len(node.value)): if clean_value[0] == node.value[i]: break left_offset += 1 for i in reversed(range(0, len(node.value))): if clean_value[-1] == node.value[i]: break right_offset += 1 guess.metadata().span = (node.span[0] - node.offset + left_offset, node.span[1] - node.offset - right_offset) return guess def found_property(node, name, value=None, confidence=1.0, update_guess=True, logger=None): # automatically retrieve the log object from the caller frame if not logger: caller_frame = inspect.stack()[1][0] logger = caller_frame.f_locals['self'].log guess = build_guess(node, name, value, confidence) return found_guess(node, guess, update_guess=update_guess, logger=logger) def found_guess(node, guess, update_guess=True, logger=None): if node.guess: if update_guess: node.guess.update_highest_confidence(guess) else: child = node.add_child(guess.metadata().span) child.guess = guess else: node.guess = guess log_found_guess(guess, logger) return node.guess def log_found_guess(guess, logger=None): for k, v in guess.items(): (logger or log).debug('Property found: %s=%s (%s) (confidence=%.2f)' % (k, v, guess.raw(k), guess.confidence(k))) class GuessFinder(object): def __init__(self, guess_func, confidence=None, logger=None, options=None): self.guess_func = guess_func self.confidence = confidence self.logger = logger or log self.options = options or {} def process_nodes(self, nodes): for node in nodes: self.process_node(node) def process_node(self, node, iterative=True, partial_span=None, skip_nodes=True): if skip_nodes and not isinstance(skip_nodes, list): skip_nodes = self.options.get('skip_nodes') elif not isinstance(skip_nodes, list): skip_nodes = [] if partial_span: value = node.value[partial_span[0]:partial_span[1]] else: value = node.value string = ' %s ' % value # add sentinels matcher_result = self.guess_func(string, node, self.options) if not matcher_result: return if not isinstance(matcher_result, Guess): result, span = matcher_result else: result, span = matcher_result, matcher_result.metadata().span #log.error('span2 %s' % (span,)) if not result: return if span[1] == len(string): # somehow, the sentinel got included in the span. Remove it span = (span[0], span[1] - 1) # readjust span to compensate for sentinels span = (span[0] - 1, span[1] - 1) # readjust span to compensate for partial_span if partial_span: span = (span[0] + partial_span[0], span[1] + partial_span[0]) if skip_nodes: skip_nodes = [skip_node for skip_node in self.options.get('skip_nodes') if skip_node.parent.span[0] == node.span[0] or skip_node.parent.span[1] == node.span[1]] # if we guessed a node that we need to skip, recurse down the tree and ignore that node indices = set() skip_nodes_spans = [] next_skip_nodes = [] for skip_node in skip_nodes: skip_for_next = False skip_nodes_spans.append(skip_node.span) if node.offset <= skip_node.span[0] <= node.span[1]: indices.add(skip_node.span[0] - node.offset) skip_for_next = True if node.offset <= skip_node.span[1] <= node.span[1]: indices.add(skip_node.span[1] - node.offset) skip_for_next = True if not skip_for_next: next_skip_nodes.append(skip_node) if indices: partition_spans = [s for s in node.get_partition_spans(indices) if s not in skip_nodes_spans] for partition_span in partition_spans: relative_span = (partition_span[0] - node.offset, partition_span[1] - node.offset) self.process_node(node, partial_span=relative_span, skip_nodes=next_skip_nodes) return # restore sentinels compensation if isinstance(result, Guess): guess = result else: no_sentinel_string =string[1:-1] guess = Guess(result, confidence=self.confidence, input=no_sentinel_string, span=span) if not iterative: found_guess(node, guess, logger=self.logger) else: absolute_span = (span[0] + node.offset, span[1] + node.offset) node.partition(span) found_child = None for child in node.children: if child.span == absolute_span: # if we have a match on one of our children, mark it as such... found_guess(child, guess, logger=self.logger) found_child = child break # ...and only then recurse on the other children for child in node.children: if child is not found_child: self.process_node(child) guessit-0.11.0/guessit/__version__.py0000664000175000017500000000150312572401301020360 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # __version__ = '0.11.0' guessit-0.11.0/guessit/slogging.py0000664000175000017500000001031412571103570017716 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from functools import wraps import logging import sys import os log = logging.getLogger(__name__) GREEN_FONT = "\x1B[0;32m" YELLOW_FONT = "\x1B[0;33m" BLUE_FONT = "\x1B[0;34m" RED_FONT = "\x1B[0;31m" RESET_FONT = "\x1B[0m" def setup_logging(colored=True, with_time=False, with_thread=False, filename=None, with_lineno=False): # pragma: no cover """Set up a nice colored logger as the main application logger.""" class SimpleFormatter(logging.Formatter): def __init__(self, with_time, with_thread): self.fmt = (('%(asctime)s ' if with_time else '') + '%(levelname)-8s ' + '[%(name)s:%(funcName)s' + (':%(lineno)s' if with_lineno else '') + ']' + ('[%(threadName)s]' if with_thread else '') + ' -- %(message)s') logging.Formatter.__init__(self, self.fmt) class ColoredFormatter(logging.Formatter): def __init__(self, with_time, with_thread): self.fmt = (('%(asctime)s ' if with_time else '') + '-CC-%(levelname)-8s ' + BLUE_FONT + '[%(name)s:%(funcName)s' + (':%(lineno)s' if with_lineno else '') + ']' + RESET_FONT + ('[%(threadName)s]' if with_thread else '') + ' -- %(message)s') logging.Formatter.__init__(self, self.fmt) def format(self, record): modpath = record.name.split('.') record.mname = modpath[0] record.mmodule = '.'.join(modpath[1:]) result = logging.Formatter.format(self, record) if record.levelno == logging.DEBUG: color = BLUE_FONT elif record.levelno == logging.INFO: color = GREEN_FONT elif record.levelno == logging.WARNING: color = YELLOW_FONT else: color = RED_FONT result = result.replace('-CC-', color) return result if filename is not None: # make sure we can write to our log file logdir = os.path.dirname(filename) if not os.path.exists(logdir): os.makedirs(logdir) ch = logging.FileHandler(filename, mode='w') ch.setFormatter(SimpleFormatter(with_time, with_thread)) else: ch = logging.StreamHandler() if colored and sys.platform != 'win32': ch.setFormatter(ColoredFormatter(with_time, with_thread)) else: ch.setFormatter(SimpleFormatter(with_time, with_thread)) logging.getLogger().addHandler(ch) def trace_func_call(f): @wraps(f) def wrapper(*args, **kwargs): is_method = (f.__name__ != f.__qualname__) # method is still not bound, we need to get around it if is_method: no_self_args = args[1:] else: no_self_args = args args_str = ', '.join(repr(arg) for arg in no_self_args) kwargs_str = ', '.join('{}={}'.format(k, v) for k, v in kwargs.items()) if not args_str: args_str = kwargs_str elif not kwargs_str: args_str = args_str else: args_str = '{}, {}'.format(args_str, kwargs_str) log.debug('Calling {}({})'.format(f.__name__, args_str)) return f(*args, **kwargs) return wrapper guessit-0.11.0/guessit/date.py0000664000175000017500000001052412534564763017043 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import datetime import re from dateutil import parser _dsep = r'[-/ \.]' _dsep_bis = r'[-/ \.x]' date_regexps = [ re.compile('%s(\d{8})%s' % (_dsep, _dsep), re.IGNORECASE), re.compile('%s(\d{6})%s' % (_dsep, _dsep), re.IGNORECASE), re.compile('[^\d](\d{2})%s(\d{1,2})%s(\d{1,2})[^\d]' % (_dsep, _dsep), re.IGNORECASE), re.compile('[^\d](\d{1,2})%s(\d{1,2})%s(\d{2})[^\d]' % (_dsep, _dsep), re.IGNORECASE), re.compile('[^\d](\d{4})%s(\d{1,2})%s(\d{1,2})[^\d]' % (_dsep_bis, _dsep), re.IGNORECASE), re.compile('[^\d](\d{1,2})%s(\d{1,2})%s(\d{4})[^\d]' % (_dsep, _dsep_bis), re.IGNORECASE), re.compile('[^\d](\d{1,2}(?:st|nd|rd|th)?%s(?:[a-z]{3,10})%s\d{4})[^\d]' % (_dsep, _dsep), re.IGNORECASE)] def valid_year(year, today=None): """Check if number is a valid year""" if not today: today = datetime.date.today() return 1920 < year < today.year + 5 def search_year(string): """Looks for year patterns, and if found return the year and group span. Assumes there are sentinels at the beginning and end of the string that always allow matching a non-digit delimiting the date. Note this only looks for valid production years, that is between 1920 and now + 5 years, so for instance 2000 would be returned as a valid year but 1492 would not. >>> search_year(' in the year 2000... ') (2000, (13, 17)) >>> search_year(' they arrived in 1492. ') (None, None) """ match = re.search(r'[^0-9]([0-9]{4})[^0-9]', string) if match: year = int(match.group(1)) if valid_year(year): return year, match.span(1) return None, None def search_date(string, year_first=None, day_first=True): """Looks for date patterns, and if found return the date and group span. Assumes there are sentinels at the beginning and end of the string that always allow matching a non-digit delimiting the date. Year can be defined on two digit only. It will return the nearest possible date from today. >>> search_date(' This happened on 2002-04-22. ') (datetime.date(2002, 4, 22), (18, 28)) >>> search_date(' And this on 17-06-1998. ') (datetime.date(1998, 6, 17), (13, 23)) >>> search_date(' no date in here ') (None, None) """ start, end = None, None match = None for date_re in date_regexps: s = date_re.search(string) if s and (match is None or s.end() - s.start() > len(match)): start, end = s.start(), s.end() if date_re.groups: match = '-'.join(s.groups()) else: match = s.group() if match is None: return None, None today = datetime.date.today() # If day_first/year_first is undefined, parse is made using both possible values. yearfirst_opts = [False, True] if year_first is not None: yearfirst_opts = [year_first] dayfirst_opts = [True, False] if day_first is not None: dayfirst_opts = [day_first] kwargs_list = ({'dayfirst': d, 'yearfirst': y} for d in dayfirst_opts for y in yearfirst_opts) for kwargs in kwargs_list: try: date = parser.parse(match, **kwargs) except (ValueError, TypeError) as e: #see https://bugs.launchpad.net/dateutil/+bug/1247643 date = None pass # check date plausibility if date and valid_year(date.year, today=today): return date.date(), (start+1, end-1) #compensate for sentinels return None, None guessit-0.11.0/guessit/fileutils.py0000664000175000017500000000550512534564763020131 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import os.path import zipfile import io from guessit import s, u def split_path(path): r"""Splits the given path into the list of folders and the filename (or the last folder if you gave it a folder path. If the given path was an absolute path, the first element will always be: - the '/' root folder on Unix systems - the drive letter on Windows systems (eg: r'C:\') - the mount point '\\' on Windows systems (eg: r'\\host\share') >>> s(split_path('/usr/bin/smewt')) ['/', 'usr', 'bin', 'smewt'] >>> s(split_path('relative_path/to/my_folder/')) ['relative_path', 'to', 'my_folder'] """ result = [] while True: head, tail = os.path.split(path) if not head and not tail: return result if not tail and head == path: # Make sure we won't have an infinite loop. result = [head] + result return result # we just split a directory ending with '/', so tail is empty if not tail: path = head continue # otherwise, add the last path fragment and keep splitting result = [tail] + result path = head def file_in_same_dir(ref_file, desired_file): """Return the path for a file in the same dir as a given reference file. >>> s(file_in_same_dir('~/smewt/smewt.db', 'smewt.settings')) == os.path.normpath('~/smewt/smewt.settings') True """ return os.path.join(*(split_path(ref_file)[:-1] + [desired_file])) def load_file_in_same_dir(ref_file, filename): """Load a given file. Works even when the file is contained inside a zip.""" path = split_path(ref_file)[:-1] + [filename] for i, p in enumerate(path): if p.endswith('.zip'): zfilename = os.path.join(*path[:i + 1]) zfile = zipfile.ZipFile(zfilename) return u(zfile.read('/'.join(path[i + 1:]))) return u(io.open(os.path.join(*path), encoding='utf-8').read()) guessit-0.11.0/guessit/test/0000775000175000017500000000000012572402126016513 5ustar toilaltoilal00000000000000guessit-0.11.0/guessit/test/test_language.py0000664000175000017500000001260312534564763021727 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.test.guessittest import * class TestLanguage(TestGuessit): def check_languages(self, languages): for lang1, lang2 in languages.items(): assert Language.fromguessit(lang1) == Language.fromguessit(lang2) def test_addic7ed(self): languages = {'English': 'en', 'English (US)': 'en-US', 'English (UK)': 'en-UK', 'Italian': 'it', 'Portuguese': 'pt', 'Portuguese (Brazilian)': 'pt-BR', 'Romanian': 'ro', 'Español (Latinoamérica)': 'es-MX', 'Español (España)': 'es-ES', 'Spanish (Latin America)': 'es-MX', 'Español': 'es', 'Spanish': 'es', 'Spanish (Spain)': 'es-ES', 'French': 'fr', 'Greek': 'el', 'Arabic': 'ar', 'German': 'de', 'Croatian': 'hr', 'Indonesian': 'id', 'Hebrew': 'he', 'Russian': 'ru', 'Turkish': 'tr', 'Swedish': 'se', 'Czech': 'cs', 'Dutch': 'nl', 'Hungarian': 'hu', 'Norwegian': 'no', 'Polish': 'pl', 'Persian': 'fa'} self.check_languages(languages) def test_subswiki(self): languages = {'English (US)': 'en-US', 'English (UK)': 'en-UK', 'English': 'en', 'French': 'fr', 'Brazilian': 'po', 'Portuguese': 'pt', 'Español (Latinoamérica)': 'es-MX', 'Español (España)': 'es-ES', 'Español': 'es', 'Italian': 'it', 'Català': 'ca'} self.check_languages(languages) def test_tvsubtitles(self): languages = {'English': 'en', 'Español': 'es', 'French': 'fr', 'German': 'de', 'Brazilian': 'br', 'Russian': 'ru', 'Ukrainian': 'ua', 'Italian': 'it', 'Greek': 'gr', 'Arabic': 'ar', 'Hungarian': 'hu', 'Polish': 'pl', 'Turkish': 'tr', 'Dutch': 'nl', 'Portuguese': 'pt', 'Swedish': 'sv', 'Danish': 'da', 'Finnish': 'fi', 'Korean': 'ko', 'Chinese': 'cn', 'Japanese': 'jp', 'Bulgarian': 'bg', 'Czech': 'cz', 'Romanian': 'ro'} self.check_languages(languages) def test_opensubtitles(self): opensubtitles_langfile = file_in_same_dir(__file__, 'opensubtitles_languages_2012_05_09.txt') for l in [u(l).strip() for l in io.open(opensubtitles_langfile, encoding='utf-8')][1:]: idlang, alpha2, _, upload_enabled, web_enabled = l.strip().split('\t') # do not test languages that are too esoteric / not widely available if int(upload_enabled) and int(web_enabled): # check that we recognize the opensubtitles language code correctly # and that we are able to output this code from a language assert idlang == Language.fromguessit(idlang).opensubtitles if alpha2: # check we recognize the opensubtitles 2-letter code correctly self.check_languages({idlang: alpha2}) def test_tmdb(self): # examples from http://api.themoviedb.org/2.1/language-tags for lang in ['en-US', 'en-CA', 'es-MX', 'fr-PF']: assert lang == str(Language.fromguessit(lang)) def test_subtitulos(self): languages = {'English (US)': 'en-US', 'English (UK)': 'en-UK', 'English': 'en', 'French': 'fr', 'Brazilian': 'po', 'Portuguese': 'pt', 'Español (Latinoamérica)': 'es-MX', 'Español (España)': 'es-ES', 'Español': 'es', 'Italian': 'it', 'Català': 'ca'} self.check_languages(languages) def test_thesubdb(self): languages = {'af': 'af', 'cs': 'cs', 'da': 'da', 'de': 'de', 'en': 'en', 'es': 'es', 'fi': 'fi', 'fr': 'fr', 'hu': 'hu', 'id': 'id', 'it': 'it', 'la': 'la', 'nl': 'nl', 'no': 'no', 'oc': 'oc', 'pl': 'pl', 'pt': 'pt', 'ro': 'ro', 'ru': 'ru', 'sl': 'sl', 'sr': 'sr', 'sv': 'sv', 'tr': 'tr'} self.check_languages(languages) def test_exceptions(self): assert Language.fromguessit('br') == Language.fromguessit('pt(br)') assert Language.fromguessit('unknown') == Language.fromguessit('und') guessit-0.11.0/guessit/test/episodes.yaml0000664000175000017500000011172112572400700021211 0ustar toilaltoilal00000000000000# Dubious tests # #? "finale " #: releaseGroup: FiNaLe # extension: "" ? Series/Californication/Season 2/Californication.2x05.Vaginatown.HDTV.XviD-0TV.avi : series: Californication season: 2 episodeNumber: 5 title: Vaginatown format: HDTV videoCodec: XviD releaseGroup: 0TV ? Series/dexter/Dexter.5x02.Hello,.Bandit.ENG.-.sub.FR.HDTV.XviD-AlFleNi-TeaM.[tvu.org.ru].avi : series: Dexter season: 5 episodeNumber: 2 title: Hello, Bandit language: English subtitleLanguage: French format: HDTV videoCodec: XviD releaseGroup: AlFleNi-TeaM website: tvu.org.ru ? Series/Treme/Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi : series: Treme season: 1 episodeNumber: 3 title: Right Place, Wrong Time format: HDTV videoCodec: XviD releaseGroup: NoTV ? Series/Duckman/Duckman - 101 (01) - 20021107 - I, Duckman.avi : series: Duckman season: 1 episodeNumber: 1 title: I, Duckman date: 2002-11-07 ? Series/Duckman/Duckman - S1E13 Joking The Chicken (unedited).avi : series: Duckman season: 1 episodeNumber: 13 title: Joking The Chicken ? Series/Simpsons/Saison 12 Français/Simpsons,.The.12x08.A.Bas.Le.Sergent.Skinner.FR.avi : series: The Simpsons season: 12 episodeNumber: 8 title: A Bas Le Sergent Skinner language: French ? Series/Futurama/Season 3 (mkv)/[™] Futurama - S03E22 - Le chef de fer à 30% ( 30 Percent Iron Chef ).mkv : series: Futurama season: 3 episodeNumber: 22 title: Le chef de fer à 30% ? Series/The Office/Season 6/The Office - S06xE01.avi : series: The Office season: 6 episodeNumber: 1 ? series/The Office/Season 4/The Office [401] Fun Run.avi : series: The Office season: 4 episodeNumber: 1 title: Fun Run ? Series/Mad Men Season 1 Complete/Mad.Men.S01E01.avi : series: Mad Men season: 1 episodeNumber: 1 other: complete ? series/Psych/Psych S02 Season 2 Complete English DVD/Psych.S02E02.65.Million.Years.Off.avi : series: Psych season: 2 episodeNumber: 2 title: 65 Million Years Off language: english format: DVD other: complete ? series/Psych/Psych S02 Season 2 Complete English DVD/Psych.S02E03.Psy.Vs.Psy.Français.srt : series: Psych season: 2 episodeNumber: 3 title: Psy Vs Psy format: DVD language: English subtitleLanguage: French other: complete ? Series/Pure Laine/Pure.Laine.1x01.Toutes.Couleurs.Unies.FR.(Québec).DVB-Kceb.[tvu.org.ru].avi : series: Pure Laine season: 1 episodeNumber: 1 title: Toutes Couleurs Unies format: DVB releaseGroup: Kceb language: french website: tvu.org.ru ? Series/Pure Laine/2x05 - Pure Laine - Je Me Souviens.avi : series: Pure Laine season: 2 episodeNumber: 5 title: Je Me Souviens ? Series/Tout sur moi/Tout sur moi - S02E02 - Ménage à trois (14-01-2008) [Rip by Ampli].avi : series: Tout sur moi season: 2 episodeNumber: 2 title: Ménage à trois date: 2008-01-14 ? The.Mentalist.2x21.18-5-4.ENG.-.sub.FR.HDTV.XviD-AlFleNi-TeaM.[tvu.org.ru].avi : series: The Mentalist season: 2 episodeNumber: 21 title: 18-5-4 language: english subtitleLanguage: french format: HDTV videoCodec: Xvid releaseGroup: AlFleNi-TeaM website: tvu.org.ru ? series/__ Incomplete __/Dr Slump (Catalan)/Dr._Slump_-_003_DVB-Rip_Catalan_by_kelf.avi : series: Dr Slump episodeNumber: 3 format: DVB language: catalan ? series/Ren and Stimpy - Black_hole_[DivX].avi : series: Ren and Stimpy title: Black hole videoCodec: DivX ? Series/Walt Disney/Donald.Duck.-.Good.Scouts.[www.bigernie.jump.to].avi : series: Donald Duck title: Good Scouts website: www.bigernie.jump.to ? Series/Neverwhere/Neverwhere.05.Down.Street.[tvu.org.ru].avi : series: Neverwhere episodeNumber: 5 title: Down Street website: tvu.org.ru ? Series/South Park/Season 4/South.Park.4x07.Cherokee.Hair.Tampons.DVDRip.[tvu.org.ru].avi : series: South Park season: 4 episodeNumber: 7 title: Cherokee Hair Tampons format: DVD website: tvu.org.ru ? Series/Kaamelott/Kaamelott - Livre V - Ep 23 - Le Forfait.avi : series: Kaamelott episodeNumber: 23 title: Le Forfait ? Series/Duckman/Duckman - 110 (10) - 20021218 - Cellar Beware.avi : series: Duckman season: 1 episodeNumber: 10 date: 2002-12-18 title: Cellar Beware ? Series/Ren & Stimpy/Ren And Stimpy - Onward & Upward-Adult Party Cartoon.avi : series: Ren And Stimpy title: Onward & Upward-Adult Party Cartoon ? Series/Breaking Bad/Minisodes/Breaking.Bad.(Minisodes).01.Good.Cop.Bad.Cop.WEBRip.XviD.avi : series: Breaking Bad episodeFormat: Minisode episodeNumber: 1 title: Good Cop Bad Cop format: WEBRip videoCodec: XviD ? Series/My Name Is Earl/My.Name.Is.Earl.S01Extras.-.Bad.Karma.DVDRip.XviD.avi : series: My Name Is Earl season: 1 title: Bad Karma format: DVD episodeDetails: Extras videoCodec: XviD ? series/Freaks And Geeks/Season 1/Episode 4 - Kim Kelly Is My Friend-eng(1).srt : series: Freaks And Geeks season: 1 episodeNumber: 4 title: Kim Kelly Is My Friend language: English ? /mnt/series/The Big Bang Theory/S01/The.Big.Bang.Theory.S01E01.mkv : series: The Big Bang Theory season: 1 episodeNumber: 1 ? /media/Parks_and_Recreation-s03-e01.mkv : series: Parks and Recreation season: 3 episodeNumber: 1 ? /media/Parks_and_Recreation-s03-e02-Flu_Season.mkv : series: Parks and Recreation season: 3 title: Flu Season episodeNumber: 2 ? /media/Parks_and_Recreation-s03-x01.mkv : series: Parks and Recreation season: 3 bonusNumber: 1 ? /media/Parks_and_Recreation-s03-x02-Gag_Reel.mkv : series: Parks and Recreation season: 3 bonusNumber: 2 bonusTitle: Gag Reel ? /media/Band_of_Brothers-e01-Currahee.mkv : series: Band of Brothers episodeNumber: 1 title: Currahee ? /media/Band_of_Brothers-x02-We_Stand_Alone_Together.mkv : series: Band of Brothers bonusNumber: 2 bonusTitle: We Stand Alone Together ? /TV Shows/Mad.M-5x9.mkv : series: Mad M season: 5 episodeNumber: 9 ? /TV Shows/new.girl.117.hdtv-lol.mp4 : series: New Girl season: 1 episodeNumber: 17 format: HDTV releaseGroup: LOL ? Kaamelott - 5x44x45x46x47x48x49x50.avi : series: Kaamelott season: 5 episodeNumber: 44 episodeList: [44, 45, 46, 47, 48, 49, 50] ? Example S01E01-02.avi : series: Example season: 1 episodeNumber: 1 episodeList: [1, 2] ? Example S01E01E02.avi : series: Example season: 1 episodeNumber: 1 episodeList: [1, 2] ? Series/Baccano!/Baccano!_-_T1_-_Trailer_-_[Ayu](dae8173e).mkv : series: Baccano! other: Trailer releaseGroup: Ayu title: T1 crc32: dae8173e ? Series/Doctor Who (2005)/Season 06/Doctor Who (2005) - S06E01 - The Impossible Astronaut (1).avi : series: Doctor Who year: 2005 season: 6 episodeNumber: 1 title: The Impossible Astronaut ? The Sopranos - [05x07] - In Camelot.mp4 : series: The Sopranos season: 5 episodeNumber: 7 title: In Camelot ? The.Office.(US).1x03.Health.Care.HDTV.XviD-LOL.avi : series: The Office (US) country: US season: 1 episodeNumber: 3 title: Health Care format: HDTV videoCodec: XviD releaseGroup: LOL ? /Volumes/data-1/Series/Futurama/Season 3/Futurama_-_S03_DVD_Bonus_-_Deleted_Scenes_Part_3.ogm : series: Futurama season: 3 part: 3 other: Bonus title: Deleted Scenes format: DVD ? Ben.and.Kate.S01E02.720p.HDTV.X264-DIMENSION.mkv : series: Ben and Kate season: 1 episodeNumber: 2 screenSize: 720p format: HDTV videoCodec: h264 releaseGroup: DIMENSION ? /volume1/TV Series/Drawn Together/Season 1/Drawn Together 1x04 Requiem for a Reality Show.avi : series: Drawn Together season: 1 episodeNumber: 4 title: Requiem for a Reality Show ? Sons.of.Anarchy.S05E06.720p.WEB.DL.DD5.1.H.264-CtrlHD.mkv : series: Sons of Anarchy season: 5 episodeNumber: 6 screenSize: 720p format: WEB-DL audioChannels: "5.1" audioCodec: DolbyDigital videoCodec: h264 releaseGroup: CtrlHD ? /media/bdc64bfe-e36f-4af8-b550-e6fd2dfaa507/TV_Shows/Doctor Who (2005)/Saison 6/Doctor Who (2005) - S06E13 - The Wedding of River Song.mkv : series: Doctor Who season: 6 episodeNumber: 13 year: 2005 title: The Wedding of River Song idNumber: bdc64bfe-e36f-4af8-b550-e6fd2dfaa507 ? /mnt/videos/tvshows/Doctor Who/Season 06/E13 - The Wedding of River Song.mkv : series: Doctor Who season: 6 episodeNumber: 13 title: The Wedding of River Song ? The.Simpsons.S24E03.Adventures.in.Baby-Getting.720p.WEB-DL.DD5.1.H.264-CtrlHD.mkv : series: The Simpsons season: 24 episodeNumber: 3 title: Adventures in Baby-Getting screenSize: 720p format: WEB-DL audioChannels: "5.1" audioCodec: DolbyDigital videoCodec: h264 releaseGroup: CtrlHD ? /home/disaster/Videos/TV/Merlin/merlin_2008.5x02.arthurs_bane_part_two.repack.720p_hdtv_x264-fov.mkv : series: Merlin season: 5 episodeNumber: 2 part: 2 title: Arthurs bane screenSize: 720p format: HDTV videoCodec: h264 releaseGroup: Fov year: 2008 other: Proper properCount: 1 ? "Da Vinci's Demons - 1x04 - The Magician.mkv" : series: "Da Vinci's Demons" season: 1 episodeNumber: 4 title: The Magician ? CSI.S013E18.Sheltered.720p.WEB-DL.DD5.1.H.264.mkv : series: CSI season: 13 episodeNumber: 18 title: Sheltered screenSize: 720p format: WEB-DL audioChannels: "5.1" audioCodec: DolbyDigital videoCodec: h264 ? Game of Thrones S03E06 1080i HDTV DD5.1 MPEG2-TrollHD.ts : series: Game of Thrones season: 3 episodeNumber: 6 screenSize: 1080i format: HDTV audioChannels: "5.1" audioCodec: DolbyDigital videoCodec: MPEG2 releaseGroup: TrollHD ? gossip.girl.s01e18.hdtv.xvid-2hd.eng.srt : series: gossip girl season: 1 episodeNumber: 18 format: HDTV videoCodec: XviD releaseGroup: 2HD subtitleLanguage: english ? Wheels.S03E01E02.720p.HDTV.x264-IMMERSE.mkv : series: Wheels season: 3 episodeNumber: 1 episodeList: [1, 2] screenSize: 720p format: HDTV videoCodec: h264 releaseGroup: IMMERSE ? Wheels.S03E01-02.720p.HDTV.x264-IMMERSE.mkv : series: Wheels season: 3 episodeNumber: 1 episodeList: [1, 2] screenSize: 720p format: HDTV videoCodec: h264 releaseGroup: IMMERSE ? Wheels.S03E01-E02.720p.HDTV.x264-IMMERSE.mkv : series: Wheels season: 3 episodeNumber: 1 episodeList: [1, 2] screenSize: 720p format: HDTV videoCodec: h264 releaseGroup: IMMERSE ? Wheels.S03E01-03.720p.HDTV.x264-IMMERSE.mkv : series: Wheels season: 3 episodeNumber: 1 episodeList: [1, 2, 3] screenSize: 720p format: HDTV videoCodec: h264 releaseGroup: IMMERSE ? Marvels.Agents.of.S.H.I.E.L.D.S01E06.720p.HDTV.X264-DIMENSION.mkv : series: Marvels Agents of S.H.I.E.L.D. season: 1 episodeNumber: 6 screenSize: 720p format: HDTV videoCodec: h264 releaseGroup: DIMENSION ? Marvels.Agents.of.S.H.I.E.L.D..S01E06.720p.HDTV.X264-DIMENSION.mkv : series: Marvels Agents of S.H.I.E.L.D. season: 1 episodeNumber: 6 screenSize: 720p format: HDTV videoCodec: h264 releaseGroup: DIMENSION ? Series/Friday Night Lights/Season 1/Friday Night Lights S01E19 - Ch-Ch-Ch-Ch-Changes.avi : series: Friday Night Lights season: 1 episodeNumber: 19 title: Ch-Ch-Ch-Ch-Changes ? Dexter Saison VII FRENCH.BDRip.XviD-MiND.nfo : series: Dexter season: 7 videoCodec: XviD language: French format: BluRay releaseGroup: MiND ? Dexter Saison sept FRENCH.BDRip.XviD-MiND.nfo : series: Dexter season: 7 videoCodec: XviD language: French format: BluRay releaseGroup: MiND ? "Pokémon S16 - E29 - 1280*720 HDTV VF.mkv" : series: Pokémon format: HDTV language: French season: 16 episodeNumber: 29 screenSize: 720p ? One.Piece.E576.VOSTFR.720p.HDTV.x264-MARINE-FORD.mkv : episodeNumber: 576 videoCodec: h264 format: HDTV series: One Piece releaseGroup: MARINE-FORD subtitleLanguage: French screenSize: 720p ? Dexter.S08E12.FINAL.MULTi.1080p.BluRay.x264-MiND.mkv : videoCodec: h264 episodeNumber: 12 season: 8 format: BluRay series: Dexter other: final language: Multiple languages releaseGroup: MiND screenSize: 1080p ? One Piece - E623 VOSTFR HD [www.manga-ddl-free.com].mkv : website: www.manga-ddl-free.com episodeNumber: 623 subtitleLanguage: French series: One Piece other: HD ? Falling Skies Saison 1.HDLight.720p.x264.VFF.mkv : language: French screenSize: 720p season: 1 series: Falling Skies videoCodec: h264 other: HDLight ? Sleepy.Hollow.S01E09.720p.WEB-DL.DD5.1.H.264-BP.mkv : episodeNumber: 9 videoCodec: h264 format: WEB-DL series: Sleepy Hollow audioChannels: "5.1" screenSize: 720p season: 1 videoProfile: BP audioCodec: DolbyDigital ? Sleepy.Hollow.S01E09.720p.WEB-DL.DD5.1.H.264-BS.mkv : episodeNumber: 9 videoCodec: h264 format: WEB-DL series: Sleepy Hollow audioChannels: "5.1" screenSize: 720p season: 1 releaseGroup: BS audioCodec: DolbyDigital ? Battlestar.Galactica.S00.Pilot.FRENCH.DVDRip.XviD-NOTAG.avi : series: Battlestar Galactica season: 0 title: Pilot episodeDetails: Pilot language: French format: DVD videoCodec: XviD releaseGroup: NOTAG ? The Big Bang Theory S00E00 Unaired Pilot VOSTFR TVRip XviD-VioCs : options: -n series: The Big Bang Theory season: 0 episodeNumber: 0 subtitleLanguage: French format: TV videoCodec: XviD releaseGroup: VioCs episodeDetails: [Unaired, Pilot] title: Unaired Pilot ? The Big Bang Theory S01E00 PROPER Unaired Pilot TVRip XviD-GIGGITY : options: -n series: The Big Bang Theory season: 1 episodeNumber: 0 format: TV videoCodec: XviD releaseGroup: GIGGITY other: proper properCount: 1 episodeDetails: [Unaired, Pilot] title: Unaired Pilot ? Pawn.Stars.S2014E18.720p.HDTV.x264-KILLERS : options: -n series: Pawn Stars season: 2014 year: 2014 episodeNumber: 18 screenSize: 720p format: HDTV videoCodec: h264 releaseGroup: KILLERS ? 2.Broke.Girls.S03E10.480p.HDTV.x264-mSD.mkv : series: 2 Broke Girls season: 3 episodeNumber: 10 screenSize: 480p format: HDTV videoCodec: h264 releaseGroup: mSD ? House.of.Cards.2013.S02E03.1080p.NF.WEBRip.DD5.1.x264-NTb.mkv : series: House of Cards year: 2013 season: 2 episodeNumber: 3 screenSize: 1080p other: Netflix format: Webrip audioChannels: "5.1" audioCodec: DolbyDigital videoCodec: h264 releaseGroup: NTb ? the.100.109.hdtv-lol.mp4 : series: the 100 season: 1 episodeNumber: 9 format: HDTV releaseGroup: lol ? Criminal.Minds.5x03.Reckoner.ENG.-.sub.FR.HDTV.XviD-STi.[tvu.org.ru].avi : series: Criminal Minds language: English subtitleLanguage: French season: 5 episodeNumber: 3 videoCodec: XviD format: HDTV website: tvu.org.ru releaseGroup: STi title: Reckoner ? 03-Criminal.Minds.avi : series: Criminal Minds episodeNumber: 3 ? '[Evil-Saizen]_Laughing_Salesman_14_[DVD][1C98686A].mkv' : crc32: 1C98686A episodeNumber: 14 format: DVD releaseGroup: Evil-Saizen series: Laughing Salesman ? '[Kaylith] Zankyou no Terror - 04 [480p][B4D4514E].mp4' : crc32: B4D4514E episodeNumber: 4 releaseGroup: Kaylith screenSize: 480p series: Zankyou no Terror ? '[PuyaSubs!] Seirei Tsukai no Blade Dance - 05 [720p][32DD560E].mkv' : crc32: 32DD560E episodeNumber: 5 releaseGroup: PuyaSubs! screenSize: 720p series: Seirei Tsukai no Blade Dance ? '[Doremi].Happiness.Charge.Precure.27.[1280x720].[DC91581A].mkv' : crc32: DC91581A episodeNumber: 27 releaseGroup: Doremi screenSize: 720p series: Happiness Charge Precure ? "[Daisei] Free!:Iwatobi Swim Club - 01 ~ (BD 720p 10-bit AAC) [99E8E009].mkv" : audioCodec: AAC crc32: 99E8E009 episodeNumber: 1 format: BluRay releaseGroup: Daisei screenSize: 720p series: Free!:Iwatobi Swim Club videoProfile: 10bit ? '[Tsundere] Boku wa Tomodachi ga Sukunai - 03 [BDRip h264 1920x1080 10bit FLAC][AF0C22CC].mkv' : audioCodec: Flac crc32: AF0C22CC episodeNumber: 3 format: BluRay releaseGroup: Tsundere screenSize: 1080p series: Boku wa Tomodachi ga Sukunai videoCodec: h264 videoProfile: 10bit ? '[t.3.3.d]_Mikakunin_de_Shinkoukei_-_12_[720p][5DDC1352].mkv' : crc32: 5DDC1352 episodeNumber: 12 screenSize: 720p series: Mikakunin de Shinkoukei releaseGroup: t.3.3.d ? '[Anime-Koi] Sabagebu! - 06 [h264-720p][ABB3728A].mkv' : crc32: ABB3728A episodeNumber: 6 releaseGroup: Anime-Koi screenSize: 720p series: Sabagebu! videoCodec: h264 ? '[aprm-Diogo4D] [BD][1080p] Nagi no Asukara 08 [4D102B7C].mkv' : crc32: 4D102B7C episodeNumber: 8 format: BluRay releaseGroup: aprm-Diogo4D screenSize: 1080p series: Nagi no Asukara ? '[Akindo-SSK] Zankyou no Terror - 05 [720P][Sub_ITA][F5CCE87C].mkv' : crc32: F5CCE87C episodeNumber: 5 releaseGroup: Akindo-SSK screenSize: 720p series: Zankyou no Terror subtitleLanguage: it ? Naruto Shippuden Episode 366 VOSTFR.avi : episodeNumber: 366 series: Naruto Shippuden subtitleLanguage: fr ? Naruto Shippuden Episode 366v2 VOSTFR.avi : episodeNumber: 366 version: 2 series: Naruto Shippuden subtitleLanguage: fr ? '[HorribleSubs] Ao Haru Ride - 06 [480p].mkv' : episodeNumber: 6 releaseGroup: HorribleSubs screenSize: 480p series: Ao Haru Ride ? '[DeadFish] Tari Tari - 01 [BD][720p][AAC].mp4' : audioCodec: AAC episodeNumber: 1 format: BluRay releaseGroup: DeadFish screenSize: 720p series: Tari Tari ? '[NoobSubs] Sword Art Online II 06 (720p 8bit AAC).mp4' : audioCodec: AAC episodeNumber: 6 releaseGroup: NoobSubs screenSize: 720p series: Sword Art Online II videoProfile: 8bit ? '[DeadFish] 01 - Tari Tari [BD][720p][AAC].mp4' : audioCodec: AAC episodeNumber: 1 format: BluRay releaseGroup: DeadFish screenSize: 720p series: Tari Tari ? '[NoobSubs] 06 Sword Art Online II (720p 8bit AAC).mp4' : audioCodec: AAC episodeNumber: 6 releaseGroup: NoobSubs screenSize: 720p series: Sword Art Online II videoProfile: 8bit ? '[DeadFish] 12 - Tari Tari [BD][720p][AAC].mp4' : audioCodec: AAC episodeNumber: 12 format: BluRay releaseGroup: DeadFish screenSize: 720p series: Tari Tari ? Something.Season.2.1of4.Ep.Title.HDTV.torrent : episodeCount: 4 episodeNumber: 1 format: HDTV season: 2 series: Something title: Title extension: torrent ? Something.Season.2of5.3of9.Ep.Title.HDTV.torrent : episodeCount: 9 episodeNumber: 3 format: HDTV season: 2 seasonCount: 5 series: Something title: Title extension: torrent ? Something.Other.Season.3of5.Complete.HDTV.torrent : format: HDTV other: Complete season: 3 seasonCount: 5 series: Something Other extension: torrent ? Something.Other.Season.1-3.avi : season: 1 seasonList: - 1 - 2 - 3 series: Something Other ? Something.Other.Season.1&3.avi : season: 1 seasonList: - 1 - 3 series: Something Other ? Something.Other.Season.1&3-1to12ep.avi : season: 1 seasonList: - 1 - 3 series: Something Other ? Something.Other.saison 1 2 & 4 a 7.avi : season: 1 seasonList: - 1 - 2 - 4 - 5 - 6 - 7 series: Something Other ? W2Test.123.HDTV.XViD-FlexGet : options: -n episodeNumber: 23 season: 1 format: HDTV releaseGroup: FlexGet series: W2Test videoCodec: XviD ? W2Test.123.HDTV.XViD-FlexGet : options: -n --episode-prefer-number episodeNumber: 123 format: HDTV releaseGroup: FlexGet series: W2Test videoCodec: XviD ? FooBar.0307.PDTV-FlexGet : options: -n --episode-prefer-number episodeNumber: 7 format: DVB releaseGroup: FlexGet season: 3 series: FooBar ? FooBar.307.PDTV-FlexGet : options: -n --episode-prefer-number episodeNumber: 307 format: DVB releaseGroup: FlexGet series: FooBar ? FooBar.07.PDTV-FlexGet : options: -n --episode-prefer-number episodeNumber: 7 format: DVB releaseGroup: FlexGet series: FooBar ? FooBar.7.PDTV-FlexGet : options: -n -t episode --episode-prefer-number episodeNumber: 7 format: DVB releaseGroup: FlexGet series: FooBar ? FooBar.0307.PDTV-FlexGet : options: -n episodeNumber: 7 format: DVB releaseGroup: FlexGet season: 3 series: FooBar ? FooBar.307.PDTV-FlexGet : options: -n episodeNumber: 7 format: DVB releaseGroup: FlexGet season: 3 series: FooBar ? FooBar.07.PDTV-FlexGet : options: -n episodeNumber: 7 format: DVB releaseGroup: FlexGet series: FooBar ? FooBar.07v4.PDTV-FlexGet : options: -n episodeNumber: 7 version: 4 format: DVB releaseGroup: FlexGet series: FooBar ? FooBar.7.PDTV-FlexGet : options: -n -t episode format: DVB releaseGroup: FlexGet series: FooBar 7 ? FooBar.7v3.PDTV-FlexGet : options: -n -t episode episodeNumber: 7 version: 3 format: DVB releaseGroup: FlexGet series: FooBar ? Test.S02E01.hdtv.real.proper : options: -n episodeNumber: 1 format: HDTV other: Proper properCount: 2 season: 2 series: Test ? Real.Test.S02E01.hdtv.proper : options: -n episodeNumber: 1 format: HDTV other: Proper properCount: 1 season: 2 series: Real Test ? Test.Real.S02E01.hdtv.proper : options: -n episodeNumber: 1 format: HDTV other: Proper properCount: 1 season: 2 series: Test Real ? Test.S02E01.hdtv.proper : options: -n episodeNumber: 1 format: HDTV other: Proper properCount: 1 season: 2 series: Test ? Test.S02E01.hdtv.real.repack.proper : options: -n episodeNumber: 1 format: HDTV other: Proper properCount: 3 season: 2 series: Test ? Date.Show.03-29-2012.HDTV.XViD-FlexGet : options: -n date: 2012-03-29 format: HDTV releaseGroup: FlexGet series: Date Show videoCodec: XviD ? Something.1x5.Season.Complete-FlexGet : options: -n episodeNumber: 5 other: Complete season: 1 series: Something releaseGroup: FlexGet ? Something Seasons 1 & 2 - Complete : options: -n other: Complete season: 1 seasonList: - 1 - 2 series: Something ? Something Seasons 4 Complete : options: -n other: Complete season: 4 series: Something ? Something.1xAll.Season.Complete-FlexGet : options: -n other: Complete season: 1 series: Something releaseGroup: FlexGet ? Something.1xAll-FlexGet : options: -n other: Complete season: 1 series: Something releaseGroup: FlexGet ? FlexGet.US.S2013E14.Title.Here.720p.HDTV.AAC5.1.x264-NOGRP : options: -n audioChannels: '5.1' audioCodec: AAC country: US episodeNumber: 14 format: HDTV releaseGroup: NOGRP screenSize: 720p season: 2013 series: FlexGet (US) title: Title Here videoCodec: h264 year: 2013 ? FlexGet.14.of.21.Title.Here.720p.HDTV.AAC5.1.x264-NOGRP : options: -n audioChannels: '5.1' audioCodec: AAC episodeCount: 21 episodeNumber: 14 format: HDTV releaseGroup: NOGRP screenSize: 720p series: FlexGet title: Title Here videoCodec: h264 ? FlexGet.Series.2013.14.of.21.Title.Here.720p.HDTV.AAC5.1.x264-NOGRP : options: -n audioChannels: '5.1' audioCodec: AAC episodeCount: 21 episodeNumber: 14 format: HDTV releaseGroup: NOGRP screenSize: 720p season: 2013 series: FlexGet title: Title Here videoCodec: h264 year: 2013 ? Something.S04E05E09 : options: -n episodeList: - 5 - 6 - 7 - 8 - 9 episodeNumber: 5 season: 4 series: Something ? FooBar 360 1080i : options: -n -t episode --episode-prefer-number episodeNumber: 360 screenSize: 1080i series: FooBar ? FooBar 360 1080i : options: -n -t episode episodeNumber: 60 season: 3 screenSize: 1080i series: FooBar ? FooBar 360 : options: -n -t episode screenSize: 360p series: FooBar ? BarFood christmas special HDTV : options: -n -t episode --expected-series BarFood format: HDTV series: BarFood title: christmas special episodeDetails: Special ? Something.2008x12.13-FlexGet : options: -n -t episode series: Something date: 2008-12-13 title: FlexGet ? '[Ignored] Test 12' : options: -n episodeNumber: 12 releaseGroup: Ignored series: Test ? '[FlexGet] Test 12' : options: -n episodeNumber: 12 releaseGroup: FlexGet series: Test ? Test.13.HDTV-Ignored : options: -n episodeNumber: 13 format: HDTV releaseGroup: Ignored series: Test ? Test.13.HDTV-Ignored : options: -n --expected-series test episodeNumber: 13 format: HDTV releaseGroup: Ignored series: Test ? Test.13.HDTV-Ignored : series: Test episodeNumber: 13 format: HDTV releaseGroup: Ignored ? Test.13.HDTV-Ignored : options: -n --expected-group "Name;FlexGet" episodeNumber: 13 format: HDTV releaseGroup: Ignored series: Test ? Test.13.HDTV-FlexGet : options: -n episodeNumber: 13 format: HDTV releaseGroup: FlexGet series: Test ? Test.14.HDTV-Name : options: -n episodeNumber: 14 format: HDTV releaseGroup: Name series: Test ? Real.Time.With.Bill.Maher.2014.10.31.HDTV.XviD-AFG.avi : date: 2014-10-31 format: HDTV releaseGroup: AFG series: Real Time With Bill Maher videoCodec: XviD ? Arrow.S03E21.Al.Sah-Him.1080p.WEB-DL.DD5.1.H.264-BS.mkv : series: Arrow season: 3 episodeNumber: 21 title: Al Sah-Him screenSize: 1080p audioCodec: DolbyDigital audioChannels: "5.1" videoCodec: h264 releaseGroup: BS format: WEB-DL ? How to Make It in America - S02E06 - I'm Sorry, Who's Yosi?.mkv : series: How to Make It in America season: 2 episodeNumber: 6 title: I'm Sorry, Who's Yosi? ? 24.S05E07.FRENCH.DVDRip.XviD-FiXi0N.avi : episodeNumber: 7 format: DVD language: fr season: 5 series: '24' videoCodec: XviD releaseGroup: FiXi0N ? 12.Monkeys.S01E12.FRENCH.BDRip.x264-VENUE.mkv : episodeNumber: 12 format: BluRay language: fr releaseGroup: VENUE season: 1 series: 12 Monkeys videoCodec: h264 ? The.Daily.Show.2015.07.01.Kirsten.Gillibrand.Extended.720p.CC.WEBRip.AAC2.0.x264-BTW.mkv : audioChannels: '2.0' audioCodec: AAC date: 2015-07-01 format: WEBRip other: CC releaseGroup: BTW screenSize: 720p series: The Daily Show title: Kirsten Gillibrand Extended videoCodec: h264 ? The.Daily.Show.2015.07.02.Sarah.Vowell.CC.WEBRip.AAC2.0.x264-BTW.mkv : audioChannels: '2.0' audioCodec: AAC date: 2015-07-02 format: WEBRip other: CC releaseGroup: BTW series: The Daily Show title: Sarah Vowell videoCodec: h264 ? 90.Day.Fiance.S02E07.I.Have.To.Tell.You.Something.720p.HDTV.x264-W4F : options: -n episodeNumber: 7 format: HDTV screenSize: 720p season: 2 series: 90 Day Fiance title: I Have To Tell You Something ? Doctor.Who.2005.S04E06.FRENCH.LD.DVDRip.XviD-TRACKS.avi : episodeNumber: 6 format: DVD language: fr releaseGroup: TRACKS season: 4 series: Doctor Who other: LD videoCodec: XviD year: 2005 ? Astro.Le.Petit.Robot.S01E01+02.FRENCH.DVDRiP.X264.INT-BOOLZ.mkv : episodeNumber: 1 episodeList: [1, 2] format: DVD language: fr releaseGroup: INT-BOOLZ season: 1 series: Astro Le Petit Robot videoCodec: h264 ? Annika.Bengtzon.2012.E01.Le.Testament.De.Nobel.FRENCH.DVDRiP.XViD-STVFRV.avi : episodeNumber: 1 format: DVD language: fr releaseGroup: STVFRV series: Annika Bengtzon title: Le Testament De Nobel videoCodec: XviD year: 2012 ? Dead.Set.02.FRENCH.LD.DVDRip.XviD-EPZ.avi : episodeNumber: 2 format: DVD language: fr other: LD releaseGroup: EPZ series: Dead Set videoCodec: XviD ? Phineas and Ferb S01E00 & S01E01 & S01E02 : options: -n episodeList: - 0 - 1 - 2 episodeNumber: 0 season: 1 series: Phineas and Ferb ? Show.Name.S01E02.S01E03.HDTV.XViD.Etc-Group : options: -n episodeList: - 2 - 3 episodeNumber: 2 format: HDTV releaseGroup: Etc-Group season: 1 series: Show Name videoCodec: XviD ? Show Name - S01E02 - S01E03 - S01E04 - Ep Name : options: -n episodeList: - 2 - 3 - 4 episodeNumber: 2 season: 1 series: Show Name title: Ep Name ? Show.Name.1x02.1x03.HDTV.XViD.Etc-Group : options: -n episodeList: - 2 - 3 episodeNumber: 2 format: HDTV releaseGroup: Etc-Group season: 1 series: Show Name videoCodec: XviD ? Show Name - 1x02 - 1x03 - 1x04 - Ep Name : options: -n episodeList: - 2 - 3 - 4 episodeNumber: 2 season: 1 series: Show Name title: Ep Name ? Show.Name.S01E02.HDTV.XViD.Etc-Group : options: -n episodeNumber: 2 format: HDTV releaseGroup: Etc-Group season: 1 series: Show Name videoCodec: XviD ? Show Name - S01E02 - My Ep Name : options: -n episodeNumber: 2 season: 1 series: Show Name title: My Ep Name ? Show Name - S01.E03 - My Ep Name : options: -n episodeNumber: 3 season: 1 series: Show Name title: My Ep Name ? Show.Name.S01E02E03.HDTV.XViD.Etc-Group : options: -n episodeList: - 2 - 3 episodeNumber: 2 format: HDTV releaseGroup: Etc-Group season: 1 series: Show Name videoCodec: XviD ? Show Name - S01E02-03 - My Ep Name : options: -n episodeList: - 2 - 3 episodeNumber: 2 season: 1 series: Show Name title: My Ep Name ? Show.Name.S01.E02.E03 : options: -n episodeList: - 2 - 3 episodeNumber: 2 season: 1 series: Show Name ? Show_Name.1x02.HDTV_XViD_Etc-Group : options: -n episodeNumber: 2 format: HDTV releaseGroup: Etc-Group season: 1 series: Show Name videoCodec: XviD ? Show Name - 1x02 - My Ep Name : options: -n episodeNumber: 2 season: 1 series: Show Name title: My Ep Name ? Show_Name.1x02x03x04.HDTV_XViD_Etc-Group : options: -n episodeList: - 2 - 3 - 4 episodeNumber: 2 format: HDTV releaseGroup: Etc-Group season: 1 series: Show Name videoCodec: XviD ? Show Name - 1x02-03-04 - My Ep Name : options: -n episodeList: - 2 - 3 - 4 episodeNumber: 2 season: 1 series: Show Name title: My Ep Name ? Show.Name.100.Event.2010.11.23.HDTV.XViD.Etc-Group : options: -n date: 2010-11-23 episodeNumber: 100 format: HDTV releaseGroup: Etc-Group series: Show Name title: Event videoCodec: XviD ? Show.Name.2010.11.23.HDTV.XViD.Etc-Group : options: -n date: 2010-11-23 format: HDTV releaseGroup: Etc-Group series: Show Name ? Show Name - 2010-11-23 - Ep Name : options: -n date: 2010-11-23 series: Show Name title: Ep Name ? Show Name Season 1 Episode 2 Ep Name : options: -n episodeNumber: 2 season: 1 series: Show Name title: Ep Name ? Show.Name.S01.HDTV.XViD.Etc-Group : options: -n format: HDTV releaseGroup: Etc-Group season: 1 series: Show Name videoCodec: XviD ? Show.Name.E02-03 : options: -n episodeNumber: 2 episodeList: - 2 - 3 series: Show Name ? Show.Name.E02.2010 : options: -n episodeNumber: 2 year: 2010 series: Show Name ? Show.Name.E23.Test : options: -n episodeNumber: 23 series: Show Name title: Test ? Show.Name.Part.3.HDTV.XViD.Etc-Group : options: -n -t episode part: 3 series: Show Name format: HDTV videoCodec: XviD releaseGroup: Etc-Group ? Show.Name.Part.1.and.Part.2.Blah-Group : options: -n -t episode part: 1 partList: - 1 - 2 series: Show Name ? Show Name - 01 - Ep Name : options: -n episodeNumber: 1 series: Show Name title: Ep Name ? 01 - Ep Name : options: -n episodeNumber: 1 series: Ep Name ? Show.Name.102.HDTV.XViD.Etc-Group : options: -n episodeNumber: 2 format: HDTV releaseGroup: Etc-Group season: 1 series: Show Name videoCodec: XviD ? '[HorribleSubs] Maria the Virgin Witch - 01 [720p].mkv' : episodeNumber: 1 releaseGroup: HorribleSubs screenSize: 720p series: Maria the Virgin Witch ? '[ISLAND]One_Piece_679_[VOSTFR]_[V1]_[8bit]_[720p]_[EB7838FC].mp4' : options: -E crc32: EB7838FC episodeNumber: 679 releaseGroup: ISLAND screenSize: 720p series: One Piece subtitleLanguage: fr videoProfile: 8bit version: 1 ? '[ISLAND]One_Piece_679_[VOSTFR]_[8bit]_[720p]_[EB7838FC].mp4' : options: -E crc32: EB7838FC episodeNumber: 679 releaseGroup: ISLAND screenSize: 720p series: One Piece subtitleLanguage: fr videoProfile: 8bit ? '[Kaerizaki-Fansub]_One_Piece_679_[VOSTFR][HD_1280x720].mp4' : options: -E episodeNumber: 679 other: HD releaseGroup: Kaerizaki-Fansub screenSize: 720p series: One Piece subtitleLanguage: fr ? '[Kaerizaki-Fansub]_One_Piece_679_[VOSTFR][FANSUB][HD_1280x720].mp4' : options: -E episodeNumber: 679 other: - Fansub - HD releaseGroup: Kaerizaki-Fansub screenSize: 720p series: One Piece subtitleLanguage: fr ? '[Kaerizaki-Fansub]_One_Piece_681_[VOSTFR][HD_1280x720]_V2.mp4' : options: -E episodeNumber: 681 other: HD releaseGroup: Kaerizaki-Fansub screenSize: 720p series: One Piece subtitleLanguage: fr version: 2 ? '[Kaerizaki-Fansub] High School DxD New 04 VOSTFR HD (1280x720) V2.mp4' : options: -E episodeNumber: 4 other: HD releaseGroup: Kaerizaki-Fansub screenSize: 720p series: High School DxD New subtitleLanguage: fr version: 2 ? '[Kaerizaki-Fansub] One Piece 603 VOSTFR PS VITA (960x544) V2.mp4' : options: -E episodeNumber: 603 releaseGroup: Kaerizaki-Fansub screenSize: 960x544 series: One Piece subtitleLanguage: fr version: 2 ? '[Group Name] Show Name.13' : options: -n episodeNumber: 13 releaseGroup: Group Name series: Show Name ? '[Group Name] Show Name - 13' : options: -n episodeNumber: 13 releaseGroup: Group Name series: Show Name ? '[Group Name] Show Name 13' : options: -n episodeNumber: 13 releaseGroup: Group Name series: Show Name # [Group Name] Show Name.13-14 # [Group Name] Show Name - 13-14 # Show Name 13-14 ? '[Stratos-Subs]_Infinite_Stratos_-_12_(1280x720_H.264_AAC)_[379759DB]' : options: -n audioCodec: AAC crc32: 379759DB episodeNumber: 12 releaseGroup: Stratos-Subs screenSize: 720p series: Infinite Stratos videoCodec: h264 # [ShinBunBu-Subs] Bleach - 02-03 (CX 1280x720 x264 AAC) ? '[SGKK] Bleach 312v1 [720p/MKV]' : options: -n episodeNumber: 312 releaseGroup: SGKK screenSize: 720p series: Bleach version: 1 ? '[Ayako]_Infinite_Stratos_-_IS_-_07_[H264][720p][EB7838FC]' : options: -n crc32: EB7838FC episodeNumber: 7 releaseGroup: Ayako screenSize: 720p series: Infinite Stratos videoCodec: h264 ? '[Ayako] Infinite Stratos - IS - 07v2 [H264][720p][44419534]' : options: -n crc32: '44419534' episodeNumber: 7 releaseGroup: Ayako screenSize: 720p series: Infinite Stratos videoCodec: h264 version: 2 ? '[Ayako-Shikkaku] Oniichan no Koto Nanka Zenzen Suki Janain Dakara ne - 10 [LQ][h264][720p] [8853B21C]' : options: -n crc32: 8853B21C episodeNumber: 10 releaseGroup: Ayako-Shikkaku screenSize: 720p series: Oniichan no Koto Nanka Zenzen Suki Janain Dakara ne videoCodec: h264 # Add support for absolute episodes ? Bleach - s16e03-04 - 313-314 : options: -n episodeList: - 3 - 4 episodeNumber: 3 season: 16 series: Bleach ? Bleach.s16e03-04.313-314 : options: -n episodeList: - 3 - 4 episodeNumber: 3 season: 16 series: Bleach ? Bleach.s16e03-04.313-314 : options: -n episodeList: - 3 - 4 episodeNumber: 3 season: 16 series: Bleach ? Bleach - 313-314 : options: -En episodeList: - 313 - 314 episodeNumber: 313 series: Bleach ? Bleach - s16e03-04 - 313-314 : options: -n episodeList: - 3 - 4 episodeNumber: 3 season: 16 series: Bleach ? Bleach.s16e03-04.313-314 : options: -n episodeList: - 3 - 4 episodeNumber: 3 season: 16 series: Bleach ? Bleach s16e03e04 313-314 : options: -n episodeList: - 3 - 4 episodeNumber: 3 season: 16 series: Bleach ? '[ShinBunBu-Subs] Bleach - 02-03 (CX 1280x720 x264 AAC)' : audioCodec: AAC episodeList: - 2 - 3 episodeNumber: 2 releaseGroup: ShinBunBu-Subs screenSize: 720p series: Bleach videoCodec: h264 ? 003. Show Name - Ep Name.ext : episodeNumber: 3 series: Show Name title: Ep Name ? 003-004. Show Name - Ep Name.ext : episodeList: - 3 - 4 episodeNumber: 3 series: Show Name title: Ep Name ? One Piece - 102 : options: -n -t episode episodeNumber: 2 season: 1 series: One Piece ? "[ACX]_Wolf's_Spirit_001.mkv" : episodeNumber: 1 releaseGroup: ACX series: "Wolf's Spirit" ? Project.Runway.S14E00.and.S14E01.(Eng.Subs).SDTV.x264-[2Maverick].mp4 : episodeList: - 0 - 1 episodeNumber: 0 format: TV releaseGroup: 2Maverick season: 14 series: Project Runway subtitleLanguage: en videoCodec: h264 ? '[Hatsuyuki-Kaitou]_Fairy_Tail_2_-_16-20_[720p][10bit].torrent' : episodeList: - 16 - 17 - 18 - 19 - 20 episodeNumber: 16 releaseGroup: Hatsuyuki-Kaitou screenSize: 720p series: Fairy Tail 2 videoProfile: 10bit ? '[Hatsuyuki-Kaitou]_Fairy_Tail_2_-_16-20_(191-195)_[720p][10bit].torrent' : options: -E episodeList: - 16 - 17 - 18 - 19 - 20 episodeNumber: 16 releaseGroup: Hatsuyuki-Kaitou screenSize: 720p series: Fairy Tail 2 ? "Looney Tunes 1940x01 Porky's Last Stand.mkv" : episodeNumber: 1 season: 1940 series: Looney Tunes title: Porky's Last Stand year: 1940 ? The.Good.Wife.S06E01.E10.720p.WEB-DL.DD5.1.H.264-CtrlHD/The.Good.Wife.S06E09.Trust.Issues.720p.WEB-DL.DD5.1.H.264-CtrlHD.mkv : audioChannels: '5.1' audioCodec: DolbyDigital episodeList: - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 episodeNumber: 9 format: WEB-DL releaseGroup: CtrlHD screenSize: 720p season: 6 series: The Good Wife title: Trust Issues videoCodec: h264 ? Fear the Walking Dead - 01x02 - So Close, Yet So Far.REPACK-KILLERS.French.C.updated.Addic7ed.com.mkv : episodeNumber: 2 language: fr other: Proper properCount: 1 season: 1 series: Fear the Walking Dead title: So Close, Yet So Far ? Fear the Walking Dead - 01x02 - En Close, Yet En Far.REPACK-KILLERS.French.C.updated.Addic7ed.com.mkv : episodeNumber: 2 language: fr other: Proper properCount: 1 season: 1 series: Fear the Walking Dead title: En Close, Yet En Far ? /av/unsorted/The.Daily.Show.2015.07.22.Jake.Gyllenhaal.720p.HDTV.x264-BATV.mkv : date: 2015-07-22 format: HDTV releaseGroup: BATV screenSize: 720p series: The Daily Show title: Jake Gyllenhaal videoCodec: h264 guessit-0.11.0/guessit/test/test_matchtree.py0000664000175000017500000000523212571103570022102 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.test.guessittest import * from guessit.transfo.guess_release_group import GuessReleaseGroup from guessit.transfo.guess_properties import GuessProperties from guessit.matchtree import BaseMatchTree keywords = yaml.load(""" ? Xvid PROPER : videoCodec: Xvid other: PROPER properCount: 1 ? PROPER-Xvid : videoCodec: Xvid other: PROPER properCount: 1 """) def guess_info(string, options=None): mtree = MatchTree(string) GuessReleaseGroup().process(mtree, options) GuessProperties().process(mtree, options) return mtree.matched() class TestMatchTree(TestGuessit): def test_base_tree(self): t = BaseMatchTree('One Two Three(Three) Four') t.partition((3, 7, 20)) leaves = list(t.leaves()) assert leaves[0].span == (0, 3) assert 'One' == leaves[0].value assert ' Two' == leaves[1].value assert ' Three(Three)' == leaves[2].value assert ' Four' == leaves[3].value leaves[2].partition((1, 6, 7, 12)) three_leaves = list(leaves[2].leaves()) assert 'Three' == three_leaves[1].value assert 'Three' == three_leaves[3].value leaves = list(t.leaves()) assert len(leaves) == 8 assert leaves[5] == three_leaves[3] assert t.previous_leaf(leaves[5]) == leaves[4] assert t.next_leaf(leaves[5]) == leaves[6] assert t.next_leaves(leaves[5]) == [leaves[6], leaves[7]] assert t.previous_leaves(leaves[5]) == [leaves[4], leaves[3], leaves[2], leaves[1], leaves[0]] assert t.next_leaf(leaves[7]) is None assert t.previous_leaf(leaves[0]) is None assert t.next_leaves(leaves[7]) == [] assert t.previous_leaves(leaves[0]) == [] def test_match(self): self.checkFields(keywords, guess_info) guessit-0.11.0/guessit/test/opensubtitles_languages_2012_05_09.txt0000664000175000017500000002253112410027040025452 0ustar toilaltoilal00000000000000IdSubLanguage ISO639 LanguageName UploadEnabled WebEnabled aar aa Afar, afar 0 0 abk ab Abkhazian 0 0 ace Achinese 0 0 ach Acoli 0 0 ada Adangme 0 0 ady adyghé 0 0 afa Afro-Asiatic (Other) 0 0 afh Afrihili 0 0 afr af Afrikaans 0 0 ain Ainu 0 0 aka ak Akan 0 0 akk Akkadian 0 0 alb sq Albanian 1 1 ale Aleut 0 0 alg Algonquian languages 0 0 alt Southern Altai 0 0 amh am Amharic 0 0 ang English, Old (ca.450-1100) 0 0 apa Apache languages 0 0 ara ar Arabic 1 1 arc Aramaic 0 0 arg an Aragonese 0 0 arm hy Armenian 1 0 arn Araucanian 0 0 arp Arapaho 0 0 art Artificial (Other) 0 0 arw Arawak 0 0 asm as Assamese 0 0 ast Asturian, Bable 0 0 ath Athapascan languages 0 0 aus Australian languages 0 0 ava av Avaric 0 0 ave ae Avestan 0 0 awa Awadhi 0 0 aym ay Aymara 0 0 aze az Azerbaijani 0 0 bad Banda 0 0 bai Bamileke languages 0 0 bak ba Bashkir 0 0 bal Baluchi 0 0 bam bm Bambara 0 0 ban Balinese 0 0 baq eu Basque 1 1 bas Basa 0 0 bat Baltic (Other) 0 0 bej Beja 0 0 bel be Belarusian 0 0 bem Bemba 0 0 ben bn Bengali 1 0 ber Berber (Other) 0 0 bho Bhojpuri 0 0 bih bh Bihari 0 0 bik Bikol 0 0 bin Bini 0 0 bis bi Bislama 0 0 bla Siksika 0 0 bnt Bantu (Other) 0 0 bos bs Bosnian 1 0 bra Braj 0 0 bre br Breton 1 0 btk Batak (Indonesia) 0 0 bua Buriat 0 0 bug Buginese 0 0 bul bg Bulgarian 1 1 bur my Burmese 0 0 byn Blin 0 0 cad Caddo 0 0 cai Central American Indian (Other) 0 0 car Carib 0 0 cat ca Catalan 1 1 cau Caucasian (Other) 0 0 ceb Cebuano 0 0 cel Celtic (Other) 0 0 cha ch Chamorro 0 0 chb Chibcha 0 0 che ce Chechen 0 0 chg Chagatai 0 0 chi zh Chinese 1 1 chk Chuukese 0 0 chm Mari 0 0 chn Chinook jargon 0 0 cho Choctaw 0 0 chp Chipewyan 0 0 chr Cherokee 0 0 chu cu Church Slavic 0 0 chv cv Chuvash 0 0 chy Cheyenne 0 0 cmc Chamic languages 0 0 cop Coptic 0 0 cor kw Cornish 0 0 cos co Corsican 0 0 cpe Creoles and pidgins, English based (Other) 0 0 cpf Creoles and pidgins, French-based (Other) 0 0 cpp Creoles and pidgins, Portuguese-based (Other) 0 0 cre cr Cree 0 0 crh Crimean Tatar 0 0 crp Creoles and pidgins (Other) 0 0 csb Kashubian 0 0 cus Cushitic (Other)' couchitiques, autres langues 0 0 cze cs Czech 1 1 dak Dakota 0 0 dan da Danish 1 1 dar Dargwa 0 0 day Dayak 0 0 del Delaware 0 0 den Slave (Athapascan) 0 0 dgr Dogrib 0 0 din Dinka 0 0 div dv Divehi 0 0 doi Dogri 0 0 dra Dravidian (Other) 0 0 dua Duala 0 0 dum Dutch, Middle (ca.1050-1350) 0 0 dut nl Dutch 1 1 dyu Dyula 0 0 dzo dz Dzongkha 0 0 efi Efik 0 0 egy Egyptian (Ancient) 0 0 eka Ekajuk 0 0 elx Elamite 0 0 eng en English 1 1 enm English, Middle (1100-1500) 0 0 epo eo Esperanto 1 0 est et Estonian 1 1 ewe ee Ewe 0 0 ewo Ewondo 0 0 fan Fang 0 0 fao fo Faroese 0 0 fat Fanti 0 0 fij fj Fijian 0 0 fil Filipino 0 0 fin fi Finnish 1 1 fiu Finno-Ugrian (Other) 0 0 fon Fon 0 0 fre fr French 1 1 frm French, Middle (ca.1400-1600) 0 0 fro French, Old (842-ca.1400) 0 0 fry fy Frisian 0 0 ful ff Fulah 0 0 fur Friulian 0 0 gaa Ga 0 0 gay Gayo 0 0 gba Gbaya 0 0 gem Germanic (Other) 0 0 geo ka Georgian 1 1 ger de German 1 1 gez Geez 0 0 gil Gilbertese 0 0 gla gd Gaelic 0 0 gle ga Irish 0 0 glg gl Galician 1 1 glv gv Manx 0 0 gmh German, Middle High (ca.1050-1500) 0 0 goh German, Old High (ca.750-1050) 0 0 gon Gondi 0 0 gor Gorontalo 0 0 got Gothic 0 0 grb Grebo 0 0 grc Greek, Ancient (to 1453) 0 0 ell el Greek 1 1 grn gn Guarani 0 0 guj gu Gujarati 0 0 gwi Gwich´in 0 0 hai Haida 0 0 hat ht Haitian 0 0 hau ha Hausa 0 0 haw Hawaiian 0 0 heb he Hebrew 1 1 her hz Herero 0 0 hil Hiligaynon 0 0 him Himachali 0 0 hin hi Hindi 1 1 hit Hittite 0 0 hmn Hmong 0 0 hmo ho Hiri Motu 0 0 hrv hr Croatian 1 1 hun hu Hungarian 1 1 hup Hupa 0 0 iba Iban 0 0 ibo ig Igbo 0 0 ice is Icelandic 1 1 ido io Ido 0 0 iii ii Sichuan Yi 0 0 ijo Ijo 0 0 iku iu Inuktitut 0 0 ile ie Interlingue 0 0 ilo Iloko 0 0 ina ia Interlingua (International Auxiliary Language Asso 0 0 inc Indic (Other) 0 0 ind id Indonesian 1 1 ine Indo-European (Other) 0 0 inh Ingush 0 0 ipk ik Inupiaq 0 0 ira Iranian (Other) 0 0 iro Iroquoian languages 0 0 ita it Italian 1 1 jav jv Javanese 0 0 jpn ja Japanese 1 1 jpr Judeo-Persian 0 0 jrb Judeo-Arabic 0 0 kaa Kara-Kalpak 0 0 kab Kabyle 0 0 kac Kachin 0 0 kal kl Kalaallisut 0 0 kam Kamba 0 0 kan kn Kannada 0 0 kar Karen 0 0 kas ks Kashmiri 0 0 kau kr Kanuri 0 0 kaw Kawi 0 0 kaz kk Kazakh 1 0 kbd Kabardian 0 0 kha Khasi 0 0 khi Khoisan (Other) 0 0 khm km Khmer 1 1 kho Khotanese 0 0 kik ki Kikuyu 0 0 kin rw Kinyarwanda 0 0 kir ky Kirghiz 0 0 kmb Kimbundu 0 0 kok Konkani 0 0 kom kv Komi 0 0 kon kg Kongo 0 0 kor ko Korean 1 1 kos Kosraean 0 0 kpe Kpelle 0 0 krc Karachay-Balkar 0 0 kro Kru 0 0 kru Kurukh 0 0 kua kj Kuanyama 0 0 kum Kumyk 0 0 kur ku Kurdish 0 0 kut Kutenai 0 0 lad Ladino 0 0 lah Lahnda 0 0 lam Lamba 0 0 lao lo Lao 0 0 lat la Latin 0 0 lav lv Latvian 1 0 lez Lezghian 0 0 lim li Limburgan 0 0 lin ln Lingala 0 0 lit lt Lithuanian 1 0 lol Mongo 0 0 loz Lozi 0 0 ltz lb Luxembourgish 1 0 lua Luba-Lulua 0 0 lub lu Luba-Katanga 0 0 lug lg Ganda 0 0 lui Luiseno 0 0 lun Lunda 0 0 luo Luo (Kenya and Tanzania) 0 0 lus lushai 0 0 mac mk Macedonian 1 1 mad Madurese 0 0 mag Magahi 0 0 mah mh Marshallese 0 0 mai Maithili 0 0 mak Makasar 0 0 mal ml Malayalam 0 0 man Mandingo 0 0 mao mi Maori 0 0 map Austronesian (Other) 0 0 mar mr Marathi 0 0 mas Masai 0 0 may ms Malay 1 1 mdf Moksha 0 0 mdr Mandar 0 0 men Mende 0 0 mga Irish, Middle (900-1200) 0 0 mic Mi'kmaq 0 0 min Minangkabau 0 0 mis Miscellaneous languages 0 0 mkh Mon-Khmer (Other) 0 0 mlg mg Malagasy 0 0 mlt mt Maltese 0 0 mnc Manchu 0 0 mni Manipuri 0 0 mno Manobo languages 0 0 moh Mohawk 0 0 mol mo Moldavian 0 0 mon mn Mongolian 1 0 mos Mossi 0 0 mwl Mirandese 0 0 mul Multiple languages 0 0 mun Munda languages 0 0 mus Creek 0 0 mwr Marwari 0 0 myn Mayan languages 0 0 myv Erzya 0 0 nah Nahuatl 0 0 nai North American Indian 0 0 nap Neapolitan 0 0 nau na Nauru 0 0 nav nv Navajo 0 0 nbl nr Ndebele, South 0 0 nde nd Ndebele, North 0 0 ndo ng Ndonga 0 0 nds Low German 0 0 nep ne Nepali 0 0 new Nepal Bhasa 0 0 nia Nias 0 0 nic Niger-Kordofanian (Other) 0 0 niu Niuean 0 0 nno nn Norwegian Nynorsk 0 0 nob nb Norwegian Bokmal 0 0 nog Nogai 0 0 non Norse, Old 0 0 nor no Norwegian 1 1 nso Northern Sotho 0 0 nub Nubian languages 0 0 nwc Classical Newari 0 0 nya ny Chichewa 0 0 nym Nyamwezi 0 0 nyn Nyankole 0 0 nyo Nyoro 0 0 nzi Nzima 0 0 oci oc Occitan 1 1 oji oj Ojibwa 0 0 ori or Oriya 0 0 orm om Oromo 0 0 osa Osage 0 0 oss os Ossetian 0 0 ota Turkish, Ottoman (1500-1928) 0 0 oto Otomian languages 0 0 paa Papuan (Other) 0 0 pag Pangasinan 0 0 pal Pahlavi 0 0 pam Pampanga 0 0 pan pa Panjabi 0 0 pap Papiamento 0 0 pau Palauan 0 0 peo Persian, Old (ca.600-400 B.C.) 0 0 per fa Persian 1 1 phi Philippine (Other) 0 0 phn Phoenician 0 0 pli pi Pali 0 0 pol pl Polish 1 1 pon Pohnpeian 0 0 por pt Portuguese 1 1 pra Prakrit languages 0 0 pro Provençal, Old (to 1500) 0 0 pus ps Pushto 0 0 que qu Quechua 0 0 raj Rajasthani 0 0 rap Rapanui 0 0 rar Rarotongan 0 0 roa Romance (Other) 0 0 roh rm Raeto-Romance 0 0 rom Romany 0 0 run rn Rundi 0 0 rup Aromanian 0 0 rus ru Russian 1 1 sad Sandawe 0 0 sag sg Sango 0 0 sah Yakut 0 0 sai South American Indian (Other) 0 0 sal Salishan languages 0 0 sam Samaritan Aramaic 0 0 san sa Sanskrit 0 0 sas Sasak 0 0 sat Santali 0 0 scc sr Serbian 1 1 scn Sicilian 0 0 sco Scots 0 0 sel Selkup 0 0 sem Semitic (Other) 0 0 sga Irish, Old (to 900) 0 0 sgn Sign Languages 0 0 shn Shan 0 0 sid Sidamo 0 0 sin si Sinhalese 1 1 sio Siouan languages 0 0 sit Sino-Tibetan (Other) 0 0 sla Slavic (Other) 0 0 slo sk Slovak 1 1 slv sl Slovenian 1 1 sma Southern Sami 0 0 sme se Northern Sami 0 0 smi Sami languages (Other) 0 0 smj Lule Sami 0 0 smn Inari Sami 0 0 smo sm Samoan 0 0 sms Skolt Sami 0 0 sna sn Shona 0 0 snd sd Sindhi 0 0 snk Soninke 0 0 sog Sogdian 0 0 som so Somali 0 0 son Songhai 0 0 sot st Sotho, Southern 0 0 spa es Spanish 1 1 srd sc Sardinian 0 0 srr Serer 0 0 ssa Nilo-Saharan (Other) 0 0 ssw ss Swati 0 0 suk Sukuma 0 0 sun su Sundanese 0 0 sus Susu 0 0 sux Sumerian 0 0 swa sw Swahili 1 0 swe sv Swedish 1 1 syr Syriac 1 0 tah ty Tahitian 0 0 tai Tai (Other) 0 0 tam ta Tamil 0 0 tat tt Tatar 0 0 tel te Telugu 0 0 tem Timne 0 0 ter Tereno 0 0 tet Tetum 0 0 tgk tg Tajik 0 0 tgl tl Tagalog 1 1 tha th Thai 1 1 tib bo Tibetan 0 0 tig Tigre 0 0 tir ti Tigrinya 0 0 tiv Tiv 0 0 tkl Tokelau 0 0 tlh Klingon 0 0 tli Tlingit 0 0 tmh Tamashek 0 0 tog Tonga (Nyasa) 0 0 ton to Tonga (Tonga Islands) 0 0 tpi Tok Pisin 0 0 tsi Tsimshian 0 0 tsn tn Tswana 0 0 tso ts Tsonga 0 0 tuk tk Turkmen 0 0 tum Tumbuka 0 0 tup Tupi languages 0 0 tur tr Turkish 1 1 tut Altaic (Other) 0 0 tvl Tuvalu 0 0 twi tw Twi 0 0 tyv Tuvinian 0 0 udm Udmurt 0 0 uga Ugaritic 0 0 uig ug Uighur 0 0 ukr uk Ukrainian 1 1 umb Umbundu 0 0 und Undetermined 0 0 urd ur Urdu 1 0 uzb uz Uzbek 0 0 vai Vai 0 0 ven ve Venda 0 0 vie vi Vietnamese 1 1 vol vo Volapük 0 0 vot Votic 0 0 wak Wakashan languages 0 0 wal Walamo 0 0 war Waray 0 0 was Washo 0 0 wel cy Welsh 0 0 wen Sorbian languages 0 0 wln wa Walloon 0 0 wol wo Wolof 0 0 xal Kalmyk 0 0 xho xh Xhosa 0 0 yao Yao 0 0 yap Yapese 0 0 yid yi Yiddish 0 0 yor yo Yoruba 0 0 ypk Yupik languages 0 0 zap Zapotec 0 0 zen Zenaga 0 0 zha za Zhuang 0 0 znd Zande 0 0 zul zu Zulu 0 0 zun Zuni 0 0 rum ro Romanian 1 1 pob pb Brazilian 1 1 guessit-0.11.0/guessit/test/test_episode.py0000664000175000017500000000214212534564763021571 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.test.guessittest import * class TestEpisode(TestGuessit): def testEpisodes(self): self.checkMinimumFieldsCorrect(filetype='episode', filename='episodes.yaml') guessit-0.11.0/guessit/test/dummy.srt0000664000175000017500000000007312410027040020365 0ustar toilaltoilal00000000000000Just a dummy srt file (used for unittests: do not remove!) guessit-0.11.0/guessit/test/__init__.py0000664000175000017500000000175312534564763020650 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals import logging from guessit.slogging import setup_logging setup_logging() logging.disable(logging.INFO) guessit-0.11.0/guessit/test/autodetect.yaml0000664000175000017500000002651612571103570021552 0ustar toilaltoilal00000000000000? Movies/Fear and Loathing in Las Vegas (1998)/Fear.and.Loathing.in.Las.Vegas.720p.HDDVD.DTS.x264-ESiR.mkv : type: movie title: Fear and Loathing in Las Vegas year: 1998 screenSize: 720p format: HD-DVD audioCodec: DTS videoCodec: h264 releaseGroup: ESiR ? Leopard.dmg : type: unknown extension: dmg ? Series/Duckman/Duckman - 101 (01) - 20021107 - I, Duckman.avi : type: episode series: Duckman season: 1 episodeNumber: 1 title: I, Duckman date: 2002-11-07 ? Series/Neverwhere/Neverwhere.05.Down.Street.[tvu.org.ru].avi : type: episode series: Neverwhere episodeNumber: 5 title: Down Street website: tvu.org.ru ? Neverwhere.05.Down.Street.[tvu.org.ru].avi : type: episode series: Neverwhere episodeNumber: 5 title: Down Street website: tvu.org.ru ? Series/Breaking Bad/Minisodes/Breaking.Bad.(Minisodes).01.Good.Cop.Bad.Cop.WEBRip.XviD.avi : type: episode series: Breaking Bad episodeFormat: Minisode episodeNumber: 1 title: Good Cop Bad Cop format: WEBRip videoCodec: XviD ? Series/Kaamelott/Kaamelott - Livre V - Ep 23 - Le Forfait.avi : type: episode series: Kaamelott episodeNumber: 23 title: Le Forfait ? Movies/The Doors (1991)/09.03.08.The.Doors.(1991).BDRip.720p.AC3.X264-HiS@SiLUHD-English.[sharethefiles.com].mkv : type: movie title: The Doors year: 1991 date: 2008-03-09 format: BluRay screenSize: 720p audioCodec: AC3 videoCodec: h264 releaseGroup: HiS@SiLUHD language: english website: sharethefiles.com ? Movies/M.A.S.H. (1970)/MASH.(1970).[Divx.5.02][Dual-Subtitulos][DVDRip].ogm : type: movie title: M.A.S.H. year: 1970 videoCodec: DivX format: DVD ? the.mentalist.501.hdtv-lol.mp4 : type: episode series: The Mentalist season: 5 episodeNumber: 1 format: HDTV releaseGroup: LOL ? the.simpsons.2401.hdtv-lol.mp4 : type: episode series: The Simpsons season: 24 episodeNumber: 1 format: HDTV releaseGroup: LOL ? Homeland.S02E01.HDTV.x264-EVOLVE.mp4 : type: episode series: Homeland season: 2 episodeNumber: 1 format: HDTV videoCodec: h264 releaseGroup: EVOLVE ? /media/Band_of_Brothers-e01-Currahee.mkv : type: episode series: Band of Brothers episodeNumber: 1 title: Currahee ? /media/Band_of_Brothers-x02-We_Stand_Alone_Together.mkv : type: episode series: Band of Brothers bonusNumber: 2 bonusTitle: We Stand Alone Together ? /movies/James_Bond-f21-Casino_Royale-x02-Stunts.mkv : type: movie title: Casino Royale filmSeries: James Bond filmNumber: 21 bonusNumber: 2 bonusTitle: Stunts ? /TV Shows/new.girl.117.hdtv-lol.mp4 : type: episode series: New Girl season: 1 episodeNumber: 17 format: HDTV releaseGroup: LOL ? The.Office.(US).1x03.Health.Care.HDTV.XviD-LOL.avi : type: episode series: The Office (US) country: US season: 1 episodeNumber: 3 title: Health Care format: HDTV videoCodec: XviD releaseGroup: LOL ? The_Insider-(1999)-x02-60_Minutes_Interview-1996.mp4 : type: movie title: The Insider year: 1999 bonusNumber: 2 bonusTitle: 60 Minutes Interview-1996 ? OSS_117--Cairo,_Nest_of_Spies.mkv : type: movie title: OSS 117--Cairo, Nest of Spies ? Rush.._Beyond_The_Lighted_Stage-x09-Between_Sun_and_Moon-2002_Hartford.mkv : type: movie title: Rush Beyond The Lighted Stage bonusNumber: 9 bonusTitle: Between Sun and Moon-2002 Hartford ? House.Hunters.International.S56E06.720p.hdtv.x264.mp4 : type: episode series: House Hunters International season: 56 episodeNumber: 6 screenSize: 720p format: HDTV videoCodec: h264 ? White.House.Down.2013.1080p.BluRay.DTS-HD.MA.5.1.x264-PublicHD.mkv : type: movie title: White House Down year: 2013 screenSize: 1080p format: BluRay audioCodec: DTS audioProfile: HDMA videoCodec: h264 releaseGroup: PublicHD audioChannels: "5.1" ? White.House.Down.2013.1080p.BluRay.DTSHD.MA.5.1.x264-PublicHD.mkv : type: movie title: White House Down year: 2013 screenSize: 1080p format: BluRay audioCodec: DTS audioProfile: HDMA videoCodec: h264 releaseGroup: PublicHD audioChannels: "5.1" ? Hostages.S01E01.Pilot.for.Air.720p.WEB-DL.DD5.1.H.264-NTb.nfo : type: episodeinfo series: Hostages title: Pilot for Air season: 1 episodeNumber: 1 screenSize: 720p format: WEB-DL audioChannels: "5.1" videoCodec: h264 audioCodec: DolbyDigital releaseGroup: NTb ? Despicable.Me.2.2013.1080p.BluRay.x264-VeDeTT.nfo : type: movieinfo title: Despicable Me 2 year: 2013 screenSize: 1080p format: BluRay videoCodec: h264 releaseGroup: VeDeTT ? Le Cinquieme Commando 1971 SUBFORCED FRENCH DVDRiP XViD AC3 Bandix.mkv : type: movie audioCodec: AC3 format: DVD releaseGroup: Bandix subtitleLanguage: French title: Le Cinquieme Commando videoCodec: XviD year: 1971 ? Le Seigneur des Anneaux - La Communauté de l'Anneau - Version Longue - BDRip.mkv : type: movie format: BluRay title: Le Seigneur des Anneaux ? La petite bande (Michel Deville - 1983) VF PAL MP4 x264 AAC.mkv : type: movie audioCodec: AAC language: French title: La petite bande videoCodec: h264 year: 1983 other: PAL ? Retour de Flammes (Gregor Schnitzler 2003) FULL DVD.iso : type: movie format: DVD title: Retour de Flammes type: movie year: 2003 ? A.Common.Title.Special.2014.avi : type: movie year: 2014 title: A Common Title Special ? A.Common.Title.2014.Special.avi : type: episode year: 2014 series: A Common Title title: Special episodeDetails: Special ? A.Common.Title.2014.Special.Edition.avi : type: movie year: 2014 title: A Common Title edition: Special Edition ? Downton.Abbey.2013.Christmas.Special.HDTV.x264-FoV.mp4 : type: episode year: 2013 series: Downton Abbey title: Christmas Special videoCodec: h264 releaseGroup: FoV format: HDTV episodeDetails: Special ? Doctor_Who_2013_Christmas_Special.The_Time_of_The_Doctor.HD : options: -n type: episode series: Doctor Who other: HD episodeDetails: Special title: Christmas Special The Time of The Doctor year: 2013 ? Doctor Who 2005 50th Anniversary Special The Day of the Doctor 3.avi : type: episode series: Doctor Who episodeDetails: Special title: 50th Anniversary Special The Day of the Doctor 3 year: 2005 ? Robot Chicken S06-Born Again Virgin Christmas Special HDTV x264.avi : type: episode series: Robot Chicken format: HDTV season: 6 title: Born Again Virgin Christmas Special videoCodec: h264 episodeDetails: Special ? Wicked.Tuna.S03E00.Head.To.Tail.Special.HDTV.x264-YesTV : options: -n type: episode series: Wicked Tuna title: Head To Tail Special releaseGroup: YesTV season: 3 episodeNumber: 0 videoCodec: h264 format: HDTV episodeDetails: Special ? The.Voice.UK.S03E12.HDTV.x264-C4TV : options: -n episodeNumber: 12 videoCodec: h264 format: HDTV series: The Voice (UK) releaseGroup: C4TV season: 3 country: United Kingdom type: episode ? /tmp/star.trek.9/star.trek.9.mkv : type: movie title: star trek 9 ? star.trek.9.mkv : type: movie title: star trek 9 ? FlexGet.S01E02.TheName.HDTV.xvid : options: -n episodeNumber: 2 format: HDTV season: 1 series: FlexGet title: TheName type: episode videoCodec: XviD ? FlexGet.S01E02.TheName.HDTV.xvid : options: -n episodeNumber: 2 format: HDTV season: 1 series: FlexGet title: TheName type: episode videoCodec: XviD ? some.series.S03E14.Title.Here.720p : options: -n episodeNumber: 14 screenSize: 720p season: 3 series: some series title: Title Here type: episode ? '[the.group] Some.Series.S03E15.Title.Two.720p' : options: -n episodeNumber: 15 releaseGroup: the.group screenSize: 720p season: 3 series: Some Series title: Title Two type: episode ? 'HD 720p: Some series.S03E16.Title.Three' : options: -n episodeNumber: 16 other: HD screenSize: 720p season: 3 series: Some series title: Title Three type: episode ? Something.Season.2.1of4.Ep.Title.HDTV.torrent : episodeCount: 4 episodeNumber: 1 format: HDTV season: 2 series: Something title: Title type: episode extension: torrent ? Show-A (US) - Episode Title S02E09 hdtv : options: -n country: US episodeNumber: 9 format: HDTV season: 2 series: Show-A (US) type: episode ? Jack's.Show.S03E01.blah.1080p : options: -n episodeNumber: 1 screenSize: 1080p season: 3 series: Jack's Show title: blah type: episode ? FlexGet.epic : options: -n title: FlexGet epic type: movie ? FlexGet.Apt.1 : options: -n title: FlexGet Apt 1 type: movie ? FlexGet.aptitude : options: -n title: FlexGet aptitude type: movie ? FlexGet.Step1 : options: -n title: FlexGet Step1 type: movie ? Movies/El Bosque Animado (1987)/El.Bosque.Animado.[Jose.Luis.Cuerda.1987].[Xvid-Dvdrip-720 * 432].avi : format: DVD screenSize: 720x432 title: El Bosque Animado videoCodec: XviD year: 1987 type: movie ? Movies/El Bosque Animado (1987)/El.Bosque.Animado.[Jose.Luis.Cuerda.1987].[Xvid-Dvdrip-720x432].avi : format: DVD screenSize: 720x432 title: El Bosque Animado videoCodec: XviD year: 1987 type: movie ? 2009.shoot.fruit.chan.multi.dvd9.pal : options: -n format: DVD language: mul other: PAL title: shoot fruit chan type: movie year: 2009 ? 2009.shoot.fruit.chan.multi.dvd5.pal : options: -n format: DVD language: mul other: PAL title: shoot fruit chan type: movie year: 2009 ? The.Flash.2014.S01E01.PREAIR.WEBRip.XviD-EVO.avi : episodeNumber: 1 format: WEBRip other: Preair releaseGroup: EVO season: 1 series: The Flash type: episode videoCodec: XviD year: 2014 ? Ice.Lake.Rebels.S01E06.Ice.Lake.Games.720p.HDTV.x264-DHD : options: -n episodeNumber: 6 format: HDTV releaseGroup: DHD screenSize: 720p season: 1 series: Ice Lake Rebels title: Ice Lake Games type: episode videoCodec: h264 ? The League - S06E10 - Epi Sexy.mkv : episodeNumber: 10 season: 6 series: The League title: Epi Sexy type: episode ? Stay (2005) [1080p]/Stay.2005.1080p.BluRay.x264.YIFY.mp4 : format: BluRay releaseGroup: YIFY screenSize: 1080p title: Stay type: movie videoCodec: h264 year: 2005 ? /media/live/A/Anger.Management.S02E82.720p.HDTV.X264-DIMENSION.mkv : format: HDTV releaseGroup: DIMENSION screenSize: 720p series: Anger Management type: episode season: 2 episodeNumber: 82 videoCodec: h264 ? "[Figmentos] Monster 34 - At the End of Darkness [781219F1].mkv" : type: episode releaseGroup: Figmentos series: Monster episodeNumber: 34 title: At the End of Darkness crc32: 781219F1 ? Game.of.Thrones.S05E07.720p.HDTV-KILLERS.mkv : type: episode episodeNumber: 7 format: HDTV releaseGroup: KILLERS screenSize: 720p season: 5 series: Game of Thrones ? Game.of.Thrones.S05E07.HDTV.720p-KILLERS.mkv : type: episode episodeNumber: 7 format: HDTV releaseGroup: KILLERS screenSize: 720p season: 5 series: Game of Thrones ? Parks and Recreation - [04x12] - Ad Campaign.avi : type: episode series: Parks and Recreation season: 4 episodeNumber: 12 title: Ad Campaign ? Star Trek Into Darkness (2013)/star.trek.into.darkness.2013.720p.web-dl.h264-publichd.mkv : type: movie title: Star Trek Into Darkness year: 2013 screenSize: 720p format: WEB-DL videoCodec: h264 releaseGroup: PublicHD ? /var/medias/series/The Originals/Season 02/The.Originals.S02E15.720p.HDTV.X264-DIMENSION.mkv : type: episode series: The Originals season: 2 episodeNumber: 15 screenSize: 720p format: HDTV videoCodec: h264 releaseGroup: DIMENSION guessit-0.11.0/guessit/test/test_movie.py0000664000175000017500000000213212534564763021257 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.test.guessittest import * class TestMovie(TestGuessit): def testMovies(self): self.checkMinimumFieldsCorrect(filetype='movie', filename='movies.yaml') guessit-0.11.0/guessit/test/test_autodetect.py0000664000175000017500000000262112534564763022304 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.test.guessittest import * class TestAutoDetect(TestGuessit): def testEmpty(self): result = guessit.guess_file_info('') assert result == {} result = guessit.guess_file_info('___-__') assert result == {} result = guessit.guess_file_info('__-.avc') assert result == {'type': 'unknown', 'extension': 'avc'} def testAutoDetect(self): self.checkMinimumFieldsCorrect(filename='autodetect.yaml', remove_type=False) guessit-0.11.0/guessit/test/test_quality.py0000664000175000017500000001074012534564763021634 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.quality import best_quality, best_quality_properties from guessit.containers import QualitiesContainer from guessit.test.guessittest import * class TestQuality(TestGuessit): def test_container(self): container = QualitiesContainer() container.register_quality('color', 'red', 10) container.register_quality('color', 'orange', 20) container.register_quality('color', 'green', 30) container.register_quality('context', 'sun', 100) container.register_quality('context', 'sea', 200) container.register_quality('context', 'sex', 300) g1 = Guess() g1['color'] = 'red' g2 = Guess() g2['color'] = 'green' g3 = Guess() g3['color'] = 'orange' q3 = container.rate_quality(g3) assert q3 == 20, "ORANGE should be rated 20. Don't ask why!" q1 = container.rate_quality(g1) q2 = container.rate_quality(g2) assert q2 > q1, "GREEN should be greater than RED. Don't ask why!" g1['context'] = 'sex' g2['context'] = 'sun' q1 = container.rate_quality(g1) q2 = container.rate_quality(g2) assert q1 > q2, "SEX should be greater than SUN. Don't ask why!" assert container.best_quality(g1, g2) == g1, "RED&SEX should be better than GREEN&SUN. Don't ask why!" assert container.best_quality_properties(['color'], g1, g2) == g2, \ "GREEN should be better than RED. Don't ask why!" assert container.best_quality_properties(['context'], g1, g2) == g1, \ "SEX should be better than SUN. Don't ask why!" q1 = container.rate_quality(g1, 'color') q2 = container.rate_quality(g2, 'color') assert q2 > q1, "GREEN should be greater than RED. Don't ask why!" container.unregister_quality('context', 'sex') container.unregister_quality('context', 'sun') q1 = container.rate_quality(g1) q2 = container.rate_quality(g2) assert q2 > q1, "GREEN&SUN should be greater than RED&SEX. Don't ask why!" g3['context'] = 'sea' container.unregister_quality('context', 'sea') q3 = container.rate_quality(g3, 'context') assert q3 == 0, "Context should be unregistered." container.unregister_quality('color') q3 = container.rate_quality(g3, 'color') assert q3 == 0, "Color should be unregistered." container.clear_qualities() q1 = container.rate_quality(g1) q2 = container.rate_quality(g2) assert q1 == q2 == 0, "Empty quality container should rate each guess to 0" def test_quality_transformers(self): guess_720p = guessit.guess_file_info("2012.2009.720p.BluRay.x264.DTS WiKi.mkv") guess_1080p = guessit.guess_file_info("2012.2009.1080p.BluRay.x264.MP3 WiKi.mkv") assert 'audioCodec' in guess_720p, "audioCodec should be present" assert 'audioCodec' in guess_1080p, "audioCodec should be present" assert 'screenSize' in guess_720p, "screenSize should be present" assert 'screenSize' in guess_1080p, "screenSize should be present" best_quality_guess = best_quality(guess_720p, guess_1080p) assert guess_1080p == best_quality_guess, "1080p+MP3 is not the best global quality" best_quality_guess = best_quality_properties(['screenSize'], guess_720p, guess_1080p) assert guess_1080p == best_quality_guess, "1080p is not the best screenSize" best_quality_guess = best_quality_properties(['audioCodec'], guess_720p, guess_1080p) assert guess_720p == best_quality_guess, "DTS is not the best audioCodec" guessit-0.11.0/guessit/test/test_autodetect_all.py0000664000175000017500000000275512534564763023144 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.test.guessittest import * IGNORE_EPISODES = [] IGNORE_MOVIES = [] class TestAutoDetectAll(TestGuessit): def testAutoMatcher(self): self.checkMinimumFieldsCorrect(filename='autodetect.yaml', remove_type=False) def testAutoMatcherMovies(self): self.checkMinimumFieldsCorrect(filename='movies.yaml', exclude_files=IGNORE_MOVIES) def testAutoMatcherEpisodes(self): self.checkMinimumFieldsCorrect(filename='episodes.yaml', exclude_files=IGNORE_EPISODES) guessit-0.11.0/guessit/test/test_api.py0000664000175000017500000000445312534564763020721 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2014 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.test.guessittest import * class TestApi(TestGuessit): def test_api(self): movie_path = 'Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv' movie_info = guessit.guess_movie_info(movie_path) video_info = guessit.guess_video_info(movie_path) episode_info = guessit.guess_episode_info(movie_path) file_info = guessit.guess_file_info(movie_path) assert guessit.guess_file_info(movie_path, type='movie') == movie_info assert guessit.guess_file_info(movie_path, type='video') == video_info assert guessit.guess_file_info(movie_path, type='episode') == episode_info assert guessit.guess_file_info(movie_path, options={'type': 'movie'}) == movie_info assert guessit.guess_file_info(movie_path, options={'type': 'video'}) == video_info assert guessit.guess_file_info(movie_path, options={'type': 'episode'}) == episode_info # kwargs priority other options assert guessit.guess_file_info(movie_path, options={'type': 'episode'}, type='movie') == episode_info movie_path_name_only = 'Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD' file_info_name_only = guessit.guess_file_info(movie_path_name_only, options={"name_only": True}) assert 'container' not in file_info_name_only assert 'container' in file_info guessit-0.11.0/guessit/test/test_utils.py0000664000175000017500000002000512534564763021277 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from datetime import date, timedelta from guessit.test.guessittest import * from guessit.fileutils import split_path from guessit.textutils import strip_brackets, str_replace, str_fill, from_camel, is_camel,\ levenshtein, reorder_title from guessit import PY2 from guessit.date import search_date, search_year class TestUtils(TestGuessit): def test_splitpath(self): alltests = {False: {'/usr/bin/smewt': ['/', 'usr', 'bin', 'smewt'], 'relative_path/to/my_folder/': ['relative_path', 'to', 'my_folder'], '//some/path': ['//', 'some', 'path'], '//some//path': ['//', 'some', 'path'], '///some////path': ['///', 'some', 'path'] }, True: {'C:\\Program Files\\Smewt\\smewt.exe': ['C:\\', 'Program Files', 'Smewt', 'smewt.exe'], 'Documents and Settings\\User\\config': ['Documents and Settings', 'User', 'config'], 'C:\\Documents and Settings\\User\\config': ['C:\\', 'Documents and Settings', 'User', 'config'], # http://bugs.python.org/issue19945 '\\\\netdrive\\share': ['\\\\', 'netdrive', 'share'] if PY2 else ['\\\\netdrive\\share'], '\\\\netdrive\\share\\folder': ['\\\\', 'netdrive', 'share', 'folder'] if PY2 else ['\\\\netdrive\\share\\', 'folder'], } } tests = alltests[sys.platform == 'win32'] for path, split in tests.items(): assert split == split_path(path) def test_strip_brackets(self): allTests = (('', ''), ('[test]', 'test'), ('{test2}', 'test2'), ('(test3)', 'test3'), ('(test4]', '(test4]'), ) for i, e in allTests: assert e == strip_brackets(i) def test_levenshtein(self): assert levenshtein("abcdef ghijk lmno", "abcdef ghijk lmno") == 0 assert levenshtein("abcdef ghijk lmnop", "abcdef ghijk lmno") == 1 assert levenshtein("abcdef ghijk lmno", "abcdef ghijk lmn") == 1 assert levenshtein("abcdef ghijk lmno", "abcdef ghijk lmnp") == 1 assert levenshtein("abcdef ghijk lmno", "abcdef ghijk lmnq") == 1 assert levenshtein("cbcdef ghijk lmno", "abcdef ghijk lmnq") == 2 assert levenshtein("cbcdef ghihk lmno", "abcdef ghijk lmnq") == 3 def test_reorder_title(self): assert reorder_title("Simpsons, The") == "The Simpsons" assert reorder_title("Simpsons,The") == "The Simpsons" assert reorder_title("Simpsons,Les", articles=('the', 'le', 'la', 'les')) == "Les Simpsons" assert reorder_title("Simpsons, Les", articles=('the', 'le', 'la', 'les')) == "Les Simpsons" def test_camel(self): assert "" == from_camel("") assert "Hello world" == str_replace("Hello World", 6, 'w') assert "Hello *****" == str_fill("Hello World", (6, 11), '*') assert "This is camel" == from_camel("ThisIsCamel") assert 'camel case' == from_camel('camelCase') assert 'A case' == from_camel('ACase') assert 'MiXedCaSe is not camel case' == from_camel('MiXedCaSe is not camelCase') assert "This is camel cased title" == from_camel("ThisIsCamelCasedTitle") assert "This is camel CASED title" == from_camel("ThisIsCamelCASEDTitle") assert "These are camel CASED title" == from_camel("TheseAreCamelCASEDTitle") assert "Give a camel case string" == from_camel("GiveACamelCaseString") assert "Death TO camel case" == from_camel("DeathTOCamelCase") assert "But i like java too:)" == from_camel("ButILikeJavaToo:)") assert "Beatdown french DVD rip.mkv" == from_camel("BeatdownFrenchDVDRip.mkv") assert "DO NOTHING ON UPPER CASE" == from_camel("DO NOTHING ON UPPER CASE") assert not is_camel("this_is_not_camel") assert is_camel("ThisIsCamel") assert "Dark.City.(1998).DC.BDRIP.720p.DTS.X264-CHD.mkv" == \ from_camel("Dark.City.(1998).DC.BDRIP.720p.DTS.X264-CHD.mkv") assert not is_camel("Dark.City.(1998).DC.BDRIP.720p.DTS.X264-CHD.mkv") assert "A2LiNE" == from_camel("A2LiNE") def test_date(self): assert search_year(' in the year 2000... ') == (2000, (13, 17)) assert search_year(' they arrived in 1492. ') == (None, None) today = date.today() today_year_2 = int(str(today.year)[2:]) future = today + timedelta(days=1000) future_year_2 = int(str(future.year)[2:]) past = today - timedelta(days=10000) past_year_2 = int(str(past.year)[2:]) assert search_date(' Something before 2002-04-22 ') == (date(2002, 4, 22), (18, 28)) assert search_date(' 2002-04-22 Something after ') == (date(2002, 4, 22), (1, 11)) assert search_date(' This happened on 2002-04-22. ') == (date(2002, 4, 22), (18, 28)) assert search_date(' This happened on 22-04-2002. ') == (date(2002, 4, 22), (18, 28)) assert search_date(' This happened on 13-04-%s. ' % (today_year_2,)) == (date(today.year, 4, 13), (18, 26)) assert search_date(' This happened on 22-04-%s. ' % (future_year_2,)) == (date(future.year, 4, 22), (18, 26)) assert search_date(' This happened on 20-04-%s. ' % past_year_2) == (date(past.year, 4, 20), (18, 26)) assert search_date(' This happened on 13-06-14. ', year_first=True) == (date(2013, 6, 14), (18, 26)) assert search_date(' This happened on 13-05-14. ', year_first=False) == (date(2014, 5, 13), (18, 26)) assert search_date(' This happened on 04-13-%s. ' % (today_year_2,)) == (date(today.year, 4, 13), (18, 26)) assert search_date(' This happened on 04-22-%s. ' % (future_year_2,)) == (date(future.year, 4, 22), (18, 26)) assert search_date(' This happened on 04-20-%s. ' % past_year_2) == (date(past.year, 4, 20), (18, 26)) assert search_date(' This happened on 35-12-%s. ' % (today_year_2,)) == (None, None) assert search_date(' This happened on 37-18-%s. ' % (future_year_2,)) == (None, None) assert search_date(' This happened on 44-42-%s. ' % past_year_2) == (None, None) assert search_date(' This happened on %s. ' % (today, )) == (today, (18, 28)) assert search_date(' This happened on %s. ' % (future, )) == (future, (18, 28)) assert search_date(' This happened on %s. ' % (past, )) == (past, (18, 28)) assert search_date(' released date: 04-03-1901? ') == (None, None) assert search_date(' There\'s no date in here. ') == (None, None) assert search_date(' Something 01-02-03 ') == (date(2003, 2, 1), (11, 19)) assert search_date(' Something 01-02-03 ', year_first=False, day_first=True) == (date(2003, 2, 1), (11, 19)) assert search_date(' Something 01-02-03 ', year_first=True) == (date(2001, 2, 3), (11, 19)) assert search_date(' Something 01-02-03 ', day_first=False) == (date(2003, 1, 2), (11, 19)) guessit-0.11.0/guessit/test/test_main.py0000664000175000017500000000443512534564763021074 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2014 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.test.guessittest import * from guessit.fileutils import file_in_same_dir from guessit import PY2 from guessit import __main__ if PY2: from StringIO import StringIO else: from io import StringIO class TestMain(TestGuessit): def setUp(self): self._stdout = sys.stdout string_out = StringIO() sys.stdout = string_out def tearDown(self): sys.stdout = self._stdout def test_list_properties(self): __main__.main(["-p"], False) __main__.main(["-V"], False) def test_list_transformers(self): __main__.main(["--transformers"], False) __main__.main(["-V", "--transformers"], False) def test_demo(self): __main__.main(["-d"], False) def test_filename(self): __main__.main(["A.Movie.2014.avi"], False) __main__.main(["A.Movie.2014.avi", "A.2nd.Movie.2014.avi"], False) __main__.main(["-y", "A.Movie.2014.avi"], False) __main__.main(["-a", "A.Movie.2014.avi"], False) __main__.main(["-v", "A.Movie.2014.avi"], False) __main__.main(["-t", "movie", "A.Movie.2014.avi"], False) __main__.main(["-t", "episode", "A.Serie.S02E06.avi"], False) __main__.main(["-i", "hash_mpc", file_in_same_dir(__file__, "1MB")], False) __main__.main(["-i", "hash_md5", file_in_same_dir(__file__, "1MB")], False) guessit-0.11.0/guessit/test/guessittest.py0000664000175000017500000001774112571103570021462 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from collections import defaultdict from unittest import TestCase, TestLoader import logging import os import sys from os.path import * import babelfish import yaml def currentPath(): """Returns the path in which the calling file is located.""" return dirname(join(os.getcwd(), sys._getframe(1).f_globals['__file__'])) def addImportPath(path): """Function that adds the specified path to the import path. The path can be absolute or relative to the calling file.""" importPath = abspath(join(currentPath(), path)) sys.path = [importPath] + sys.path log = logging.getLogger(__name__) from guessit.options import get_opts from guessit import base_text_type from guessit import * from guessit.matcher import * from guessit.fileutils import * import guessit def allTests(testClass): return TestLoader().loadTestsFromTestCase(testClass) class TestGuessit(TestCase): def checkMinimumFieldsCorrect(self, filename, filetype=None, remove_type=True, exclude_files=None): groundTruth = yaml.load(load_file_in_same_dir(__file__, filename)) def guess_func(string, options=None): return guess_file_info(string, options=options, type=filetype) return self.checkFields(groundTruth, guess_func, remove_type, exclude_files) def checkFields(self, groundTruth, guess_func, remove_type=True, exclude_files=None): total = 0 exclude_files = exclude_files or [] fails = defaultdict(list) additionals = defaultdict(list) for filename, required_fields in groundTruth.items(): filename = u(filename) if filename in exclude_files: continue log.debug('\n' + '-' * 120) log.info('Guessing information for file: %s' % filename) options = required_fields.pop('options') if 'options' in required_fields else None try: found = guess_func(filename, options) except Exception as e: fails[filename].append("An exception has occured in %s: %s" % (filename, e)) log.exception("An exception has occured in %s: %s" % (filename, e)) continue total += 1 # no need for these in the unittests if remove_type: try: del found['type'] except: pass for prop in ('container', 'mimetype', 'unidentified'): if prop in found: del found[prop] # props which are list of just 1 elem should be opened for easier writing of the tests for prop in ('language', 'subtitleLanguage', 'other', 'episodeDetails', 'unidentified'): value = found.get(prop, None) if isinstance(value, list) and len(value) == 1: found[prop] = value[0] # look for missing properties for prop, value in required_fields.items(): if prop not in found: log.debug("Prop '%s' not found in: %s" % (prop, filename)) fails[filename].append("'%s' not found in: %s" % (prop, filename)) continue # if both properties are strings, do a case-insensitive comparison if (isinstance(value, base_text_type) and isinstance(found[prop], base_text_type)): if value.lower() != found[prop].lower(): log.debug("Wrong prop value [str] for '%s': expected = '%s' - received = '%s'" % (prop, u(value), u(found[prop]))) fails[filename].append("'%s': expected = '%s' - received = '%s'" % (prop, u(value), u(found[prop]))) elif isinstance(value, list) and isinstance(found[prop], list): if found[prop] and isinstance(found[prop][0], babelfish.Language): # list of languages s1 = set(Language.fromguessit(s) for s in value) s2 = set(found[prop]) else: # by default we assume list of strings and do a case-insensitive # comparison on their elements s1 = set(u(s).lower() for s in value) s2 = set(u(s).lower() for s in found[prop]) if s1 != s2: log.debug("Wrong prop value [list] for '%s': expected = '%s' - received = '%s'" % (prop, u(value), u(found[prop]))) fails[filename].append("'%s': expected = '%s' - received = '%s'" % (prop, u(value), u(found[prop]))) elif isinstance(found[prop], babelfish.Language): try: if babelfish.Language.fromguessit(value) != found[prop]: raise ValueError except: log.debug("Wrong prop value [Language] for '%s': expected = '%s' - received = '%s'" % (prop, u(value), u(found[prop]))) fails[filename].append("'%s': expected = '%s' - received = '%s'" % (prop, u(value), u(found[prop]))) elif isinstance(found[prop], babelfish.Country): try: if babelfish.Country.fromguessit(value) != found[prop]: raise ValueError except: log.debug("Wrong prop value [Country] for '%s': expected = '%s' - received = '%s'" % (prop, u(value), u(found[prop]))) fails[filename].append("'%s': expected = '%s' - received = '%s'" % (prop, u(value), u(found[prop]))) # otherwise, just compare their values directly else: if found[prop] != value: log.debug("Wrong prop value for '%s': expected = '%s' [%s] - received = '%s' [%s]" % (prop, u(value), type(value), u(found[prop]), type(found[prop]))) fails[filename].append("'%s': expected = '%s' [%s] - received = '%s' [%s]" % (prop, u(value), type(value), u(found[prop]), type(found[prop]))) # look for additional properties for prop, value in found.items(): if prop not in required_fields: log.debug("Found additional info for prop = '%s': '%s'" % (prop, u(value))) additionals[filename].append("'%s': '%s'" % (prop, u(value))) correct = total - len(fails) log.info('SUMMARY: Guessed correctly %d out of %d filenames' % (correct, total)) for failed_entry, failed_properties in fails.items(): log.error('---- ' + failed_entry + ' ----') for failed_property in failed_properties: log.error("FAILED: " + failed_property) for additional_entry, additional_properties in additionals.items(): log.warning('---- ' + additional_entry + ' ----') for additional_property in additional_properties: log.warning("ADDITIONAL: " + additional_property) assert correct == total, 'Correct: %d < Total: %d' % (correct, total) guessit-0.11.0/guessit/test/movies.yaml0000664000175000017500000004251212571103570020705 0ustar toilaltoilal00000000000000 ? Movies/Fear and Loathing in Las Vegas (1998)/Fear.and.Loathing.in.Las.Vegas.720p.HDDVD.DTS.x264-ESiR.mkv : title: Fear and Loathing in Las Vegas year: 1998 screenSize: 720p format: HD-DVD audioCodec: DTS videoCodec: h264 releaseGroup: ESiR ? Movies/El Dia de la Bestia (1995)/El.dia.de.la.bestia.DVDrip.Spanish.DivX.by.Artik[SEDG].avi : title: El Dia de la Bestia year: 1995 format: DVD language: spanish videoCodec: DivX releaseGroup: Artik[SEDG] ? Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv : title: Dark City year: 1998 format: BluRay screenSize: 720p audioCodec: DTS videoCodec: h264 releaseGroup: CHD ? Movies/Sin City (BluRay) (2005)/Sin.City.2005.BDRip.720p.x264.AC3-SEPTiC.mkv : title: Sin City year: 2005 format: BluRay screenSize: 720p videoCodec: h264 audioCodec: AC3 releaseGroup: SEPTiC ? Movies/Borat (2006)/Borat.(2006).R5.PROPER.REPACK.DVDRip.XviD-PUKKA.avi : title: Borat year: 2006 other: PROPER properCount: 2 format: DVD other: [ R5, Proper ] videoCodec: XviD releaseGroup: PUKKA ? "[XCT].Le.Prestige.(The.Prestige).DVDRip.[x264.HP.He-Aac.{Fr-Eng}.St{Fr-Eng}.Chaps].mkv" : title: Le Prestige format: DVD videoCodec: h264 videoProfile: HP audioCodec: AAC audioProfile: HE language: [ french, english ] subtitleLanguage: [ french, english ] releaseGroup: XCT ? Battle Royale (2000)/Battle.Royale.(Batoru.Rowaiaru).(2000).(Special.Edition).CD1of2.DVDRiP.XviD-[ZeaL].avi : title: Battle Royale year: 2000 edition: special edition cdNumber: 1 cdNumberTotal: 2 format: DVD videoCodec: XviD releaseGroup: ZeaL ? Movies/Brazil (1985)/Brazil_Criterion_Edition_(1985).CD2.avi : title: Brazil edition: Criterion Edition year: 1985 cdNumber: 2 ? Movies/Persepolis (2007)/[XCT] Persepolis [H264+Aac-128(Fr-Eng)+ST(Fr-Eng)+Ind].mkv : title: Persepolis year: 2007 videoCodec: h264 audioCodec: AAC language: [ French, English ] subtitleLanguage: [ French, English ] releaseGroup: XCT ? Movies/Toy Story (1995)/Toy Story [HDTV 720p English-Spanish].mkv : title: Toy Story year: 1995 format: HDTV screenSize: 720p language: [ english, spanish ] ? Movies/Office Space (1999)/Office.Space.[Dual-DVDRip].[Spanish-English].[XviD-AC3-AC3].[by.Oswald].avi : title: Office Space year: 1999 format: DVD language: [ english, spanish ] videoCodec: XviD audioCodec: AC3 ? Movies/Wild Zero (2000)/Wild.Zero.DVDivX-EPiC.avi : title: Wild Zero year: 2000 videoCodec: DivX releaseGroup: EPiC ? movies/Baraka_Edition_Collector.avi : title: Baraka edition: collector edition ? Movies/Blade Runner (1982)/Blade.Runner.(1982).(Director's.Cut).CD1.DVDRip.XviD.AC3-WAF.avi : title: Blade Runner year: 1982 edition: Director's Cut cdNumber: 1 format: DVD videoCodec: XviD audioCodec: AC3 releaseGroup: WAF ? movies/American.The.Bill.Hicks.Story.2009.DVDRip.XviD-EPiSODE.[UsaBit.com]/UsaBit.com_esd-americanbh.avi : title: American The Bill Hicks Story year: 2009 format: DVD videoCodec: XviD releaseGroup: EPiSODE website: UsaBit.com ? movies/Charlie.And.Boots.DVDRip.XviD-TheWretched/wthd-cab.avi : title: Charlie And Boots format: DVD videoCodec: XviD releaseGroup: TheWretched ? movies/Steig Larsson Millenium Trilogy (2009) BRrip 720 AAC x264/(1)The Girl With The Dragon Tattoo (2009) BRrip 720 AAC x264.mkv : title: The Girl With The Dragon Tattoo filmSeries: Steig Larsson Millenium Trilogy filmNumber: 1 year: 2009 format: BluRay audioCodec: AAC videoCodec: h264 screenSize: 720p ? movies/Greenberg.REPACK.LiMiTED.DVDRip.XviD-ARROW/arw-repack-greenberg.dvdrip.xvid.avi : title: Greenberg format: DVD videoCodec: XviD releaseGroup: ARROW other: ['Proper', 'Limited'] properCount: 2 ? Movies/Fr - Paris 2054, Renaissance (2005) - De Christian Volckman - (Film Divx Science Fiction Fantastique Thriller Policier N&B).avi : title: Paris 2054, Renaissance year: 2005 language: french videoCodec: DivX ? Movies/[阿维达].Avida.2006.FRENCH.DVDRiP.XViD-PROD.avi : title: Avida year: 2006 language: french format: DVD videoCodec: XviD releaseGroup: PROD ? Movies/Alice in Wonderland DVDRip.XviD-DiAMOND/dmd-aw.avi : title: Alice in Wonderland format: DVD videoCodec: XviD releaseGroup: DiAMOND ? Movies/Ne.Le.Dis.A.Personne.Fr 2 cd/personnea_mp.avi : title: Ne Le Dis A Personne language: french cdNumberTotal: 2 ? Movies/Bunker Palace Hôtel (Enki Bilal) (1989)/Enki Bilal - Bunker Palace Hotel (Fr Vhs Rip).avi : title: Bunker Palace Hôtel year: 1989 language: french format: VHS ? Movies/21 (2008)/21.(2008).DVDRip.x264.AC3-FtS.[sharethefiles.com].mkv : title: "21" year: 2008 format: DVD videoCodec: h264 audioCodec: AC3 releaseGroup: FtS website: sharethefiles.com ? Movies/9 (2009)/9.2009.Blu-ray.DTS.720p.x264.HDBRiSe.[sharethefiles.com].mkv : title: "9" year: 2009 format: BluRay audioCodec: DTS screenSize: 720p videoCodec: h264 releaseGroup: HDBRiSe website: sharethefiles.com ? Movies/Mamma.Mia.2008.DVDRip.AC3.XviD-CrazyTeam/Mamma.Mia.2008.DVDRip.AC3.XviD-CrazyTeam.avi : title: Mamma Mia year: 2008 format: DVD audioCodec: AC3 videoCodec: XviD releaseGroup: CrazyTeam ? Movies/M.A.S.H. (1970)/MASH.(1970).[Divx.5.02][Dual-Subtitulos][DVDRip].ogm : title: M.A.S.H. year: 1970 videoCodec: DivX format: DVD ? Movies/The Doors (1991)/09.03.08.The.Doors.(1991).BDRip.720p.AC3.X264-HiS@SiLUHD-English.[sharethefiles.com].mkv : title: The Doors year: 1991 date: 2008-03-09 format: BluRay screenSize: 720p audioCodec: AC3 videoCodec: h264 releaseGroup: HiS@SiLUHD language: english website: sharethefiles.com ? Movies/The Doors (1991)/08.03.09.The.Doors.(1991).BDRip.720p.AC3.X264-HiS@SiLUHD-English.[sharethefiles.com].mkv : options: --date-year-first title: The Doors year: 1991 date: 2008-03-09 format: BluRay screenSize: 720p audioCodec: AC3 videoCodec: h264 releaseGroup: HiS@SiLUHD language: english website: sharethefiles.com ? Movies/Ratatouille/video_ts-ratatouille.srt : title: Ratatouille format: DVD ? Movies/001 __ A classer/Fantomas se déchaine - Louis de Funès.avi : title: Fantomas se déchaine ? Movies/Comme une Image (2004)/Comme.Une.Image.FRENCH.DVDRiP.XViD-NTK.par-www.divx-overnet.com.avi : title: Comme une Image year: 2004 language: french format: DVD videoCodec: XviD releaseGroup: NTK website: www.divx-overnet.com ? Movies/Fantastic Mr Fox/Fantastic.Mr.Fox.2009.DVDRip.{x264+LC-AAC.5.1}{Fr-Eng}{Sub.Fr-Eng}-™.[sharethefiles.com].mkv : title: Fantastic Mr Fox year: 2009 format: DVD videoCodec: h264 audioCodec: AAC audioProfile: LC audioChannels: "5.1" language: [ french, english ] subtitleLanguage: [ french, english ] website: sharethefiles.com ? Movies/Somewhere.2010.DVDRip.XviD-iLG/i-smwhr.avi : title: Somewhere year: 2010 format: DVD videoCodec: XviD releaseGroup: iLG ? Movies/Moon_(2009).mkv : title: Moon year: 2009 ? Movies/Moon_(2009)-x01.mkv : title: Moon year: 2009 bonusNumber: 1 ? Movies/Moon_(2009)-x02-Making_Of.mkv : title: Moon year: 2009 bonusNumber: 2 bonusTitle: Making Of ? movies/James_Bond-f17-Goldeneye.mkv : title: Goldeneye filmSeries: James Bond filmNumber: 17 ? /movies/James_Bond-f21-Casino_Royale.mkv : title: Casino Royale filmSeries: James Bond filmNumber: 21 ? /movies/James_Bond-f21-Casino_Royale-x01-Becoming_Bond.mkv : title: Casino Royale filmSeries: James Bond filmNumber: 21 bonusNumber: 1 bonusTitle: Becoming Bond ? /movies/James_Bond-f21-Casino_Royale-x02-Stunts.mkv : title: Casino Royale filmSeries: James Bond filmNumber: 21 bonusNumber: 2 bonusTitle: Stunts ? OSS_117--Cairo,_Nest_of_Spies.mkv : title: OSS 117--Cairo, Nest of Spies ? The Godfather Part III.mkv : title: The Godfather part: 3 ? Foobar Part VI.mkv : title: Foobar part: 6 ? The_Insider-(1999)-x02-60_Minutes_Interview-1996.mp4 : title: The Insider year: 1999 bonusNumber: 2 bonusTitle: 60 Minutes Interview-1996 ? Rush.._Beyond_The_Lighted_Stage-x09-Between_Sun_and_Moon-2002_Hartford.mkv : title: Rush Beyond The Lighted Stage bonusNumber: 9 bonusTitle: Between Sun and Moon-2002 Hartford ? /public/uTorrent/Downloads Finished/Movies/Indiana.Jones.and.the.Temple.of.Doom.1984.HDTV.720p.x264.AC3.5.1-REDµX/Indiana.Jones.and.the.Temple.of.Doom.1984.HDTV.720p.x264.AC3.5.1-REDµX.mkv : title: Indiana Jones and the Temple of Doom year: 1984 format: HDTV screenSize: 720p videoCodec: h264 audioCodec: AC3 audioChannels: "5.1" releaseGroup: REDµX ? The.Director’s.Notebook.2006.Blu-Ray.x264.DXVA.720p.AC3-de[42].mkv : title: The Director’s Notebook year: 2006 format: BluRay videoCodec: h264 videoApi: DXVA screenSize: 720p audioCodec: AC3 releaseGroup: de[42] ? Movies/Cosmopolis.2012.LiMiTED.720p.BluRay.x264-AN0NYM0US[bb]/ano-cosmo.720p.mkv : title: Cosmopolis year: 2012 screenSize: 720p videoCodec: h264 releaseGroup: AN0NYM0US[bb] format: BluRay other: LIMITED ? movies/La Science des Rêves (2006)/La.Science.Des.Reves.FRENCH.DVDRip.XviD-MP-AceBot.avi : title: La Science des Rêves year: 2006 format: DVD videoCodec: XviD videoProfile: MP releaseGroup: AceBot language: French ? The_Italian_Job.mkv : title: The Italian Job ? The.Rum.Diary.2011.1080p.BluRay.DTS.x264.D-Z0N3.mkv : title: The Rum Diary year: 2011 screenSize: 1080p format: BluRay videoCodec: h264 audioCodec: DTS releaseGroup: D-Z0N3 ? Life.Of.Pi.2012.1080p.BluRay.DTS.x264.D-Z0N3.mkv : title: Life Of Pi year: 2012 screenSize: 1080p format: BluRay videoCodec: h264 audioCodec: DTS releaseGroup: D-Z0N3 ? The.Kings.Speech.2010.1080p.BluRay.DTS.x264.D Z0N3.mkv : title: The Kings Speech year: 2010 screenSize: 1080p format: BluRay audioCodec: DTS videoCodec: h264 releaseGroup: D Z0N3 ? Street.Kings.2008.BluRay.1080p.DTS.x264.dxva EuReKA.mkv : title: Street Kings year: 2008 format: BluRay screenSize: 1080p audioCodec: DTS videoCodec: h264 videoApi: DXVA releaseGroup: EuReKa ? 2001.A.Space.Odyssey.1968.HDDVD.1080p.DTS.x264.dxva EuReKA.mkv : title: 2001 A Space Odyssey year: 1968 format: HD-DVD screenSize: 1080p audioCodec: DTS videoCodec: h264 videoApi: DXVA releaseGroup: EuReKa ? 2012.2009.720p.BluRay.x264.DTS WiKi.mkv : title: "2012" year: 2009 screenSize: 720p format: BluRay videoCodec: h264 audioCodec: DTS releaseGroup: WiKi ? /share/Download/movie/Dead Man Down (2013) BRRiP XViD DD5_1 Custom NLSubs =-_lt Q_o_Q gt-=_/XD607ebb-BRc59935-5155473f-1c5f49/XD607ebb-BRc59935-5155473f-1c5f49.avi : title: Dead Man Down year: 2013 format: BluRay videoCodec: XviD audioChannels: "5.1" audioCodec: DolbyDigital idNumber: XD607ebb-BRc59935-5155473f-1c5f49 ? Pacific.Rim.3D.2013.COMPLETE.BLURAY-PCH.avi : title: Pacific Rim year: 2013 format: BluRay other: - complete - 3D releaseGroup: PCH ? Immersion.French.2011.STV.READNFO.QC.FRENCH.ENGLISH.NTSC.DVDR.nfo : title: Immersion French year: 2011 language: - French - English format: DVD other: NTSC ? Immersion.French.2011.STV.READNFO.QC.FRENCH.NTSC.DVDR.nfo : title: Immersion French year: 2011 language: French format: DVD other: NTSC ? Immersion.French.2011.STV.READNFO.QC.NTSC.DVDR.nfo : title: Immersion French year: 2011 format: DVD other: NTSC ? French.Immersion.2011.STV.READNFO.QC.ENGLISH.NTSC.DVDR.nfo : title: French Immersion year: 2011 language: ENGLISH format: DVD other: NTSC ? Howl's_Moving_Castle_(2004)_[720p,HDTV,x264,DTS]-FlexGet.avi : videoCodec: h264 format: HDTV title: Howl's Moving Castle screenSize: 720p year: 2004 audioCodec: DTS releaseGroup: FlexGet ? Pirates de langkasuka.2008.FRENCH.1920X1080.h264.AVC.AsiaRa.mkv : screenSize: 1080p year: 2008 language: French videoCodec: h264 title: Pirates de langkasuka releaseGroup: AsiaRa ? Masala (2013) Telugu Movie HD DVDScr XviD - Exclusive.avi : year: 2013 videoCodec: XviD title: Masala format: HD-DVD other: screener language: Telugu releaseGroup: Exclusive ? Django Unchained 2012 DVDSCR X264 AAC-P2P.nfo : year: 2012 other: screener videoCodec: h264 title: Django Unchained audioCodec: AAC format: DVD releaseGroup: P2P ? Ejecutiva.En.Apuros(2009).BLURAY.SCR.Xvid.Spanish.LanzamientosD.nfo : year: 2009 other: screener format: BluRay videoCodec: XviD language: Spanish title: Ejecutiva En Apuros ? Die.Schluempfe.2.German.DL.1080p.BluRay.x264-EXQUiSiTE.mkv : title: Die Schluempfe 2 format: BluRay language: - Multiple languages - German videoCodec: h264 releaseGroup: EXQUiSiTE screenSize: 1080p ? Rocky 1976 French SubForced BRRip x264 AC3-FUNKY.mkv : title: Rocky year: 1976 subtitleLanguage: French format: BluRay videoCodec: h264 audioCodec: AC3 releaseGroup: FUNKY ? REDLINE (BD 1080p H264 10bit FLAC) [3xR].mkv : title: REDLINE format: BluRay videoCodec: h264 videoProfile: 10bit audioCodec: Flac screenSize: 1080p ? The.Lizzie.McGuire.Movie.(2003).HR.DVDRiP.avi : title: The Lizzie McGuire Movie year: 2003 format: DVD other: HR ? Hua.Mulan.BRRIP.MP4.x264.720p-HR.avi : title: Hua Mulan videoCodec: h264 format: BluRay screenSize: 720p other: HR ? Dr.Seuss.The.Lorax.2012.DVDRip.LiNE.XviD.AC3.HQ.Hive-CM8.mp4 : videoCodec: XviD title: Dr Seuss The Lorax format: DVD other: LiNE year: 2012 audioCodec: AC3 audioProfile: HQ releaseGroup: Hive-CM8 ? "Star Wars: Episode IV - A New Hope (2004) Special Edition.MKV" : title: Star Wars Episode IV year: 2004 edition: Special Edition ? Dr.LiNE.The.Lorax.2012.DVDRip.LiNE.XviD.AC3.HQ.Hive-CM8.mp4 : videoCodec: XviD title: Dr LiNE The Lorax format: DVD other: LiNE year: 2012 audioCodec: AC3 audioProfile: HQ releaseGroup: Hive-CM8 ? Perfect Child-2007-TRUEFRENCH-TVRip.Xvid-h@mster.avi : releaseGroup: h@mster title: Perfect Child videoCodec: XviD language: French format: TV year: 2007 ? entre.ciel.et.terre.(1994).dvdrip.h264.aac-psypeon.avi : audioCodec: AAC format: DVD releaseGroup: psypeon title: entre ciel et terre videoCodec: h264 year: 1994 ? Yves.Saint.Laurent.2013.FRENCH.DVDSCR.MD.XviD-ViVARiUM.avi : format: DVD language: French other: - MD - Screener releaseGroup: ViVARiUM title: Yves Saint Laurent videoCodec: XviD year: 2013 ? Echec et Mort - Hard to Kill - Steven Seagal Multi 1080p BluRay x264 CCATS.avi : format: BluRay language: Multiple languages releaseGroup: CCATS screenSize: 1080p title: Echec et Mort videoCodec: h264 ? Paparazzi - Timsit/Lindon (MKV 1080p tvripHD) : options: -n title: Paparazzi screenSize: 1080p format: HDTV ? some.movie.720p.bluray.x264-mind : options: -n title: some movie screenSize: 720p videoCodec: h264 releaseGroup: mind format: BluRay ? Dr LiNE The Lorax 720p h264 BluRay : options: -n title: Dr LiNE The Lorax screenSize: 720p videoCodec: h264 format: BluRay ? BeatdownFrenchDVDRip.mkv : options: -c title: Beatdown language: French format: DVD ? YvesSaintLaurent2013FrenchDVDScrXvid.avi : options: -c format: DVD language: French other: Screener title: Yves saint laurent videoCodec: XviD year: 2013 ? Elle.s.en.va.720p.mkv : screenSize: 720p title: Elle s en va ? FooBar.7.PDTV-FlexGet : options: -n format: DVB releaseGroup: FlexGet title: FooBar 7 ? h265 - HEVC Riddick Unrated Director Cut French 1080p DTS.mkv : audioCodec: DTS edition: Director's cut language: fr screenSize: 1080p title: Riddick Unrated videoCodec: h265 ? "[h265 - HEVC] Riddick Unrated Director Cut French [1080p DTS].mkv" : audioCodec: DTS edition: Director's cut language: fr screenSize: 1080p title: Riddick Unrated videoCodec: h265 ? Barbecue-2014-French-mHD-1080p : options: -n language: fr other: mHD screenSize: 1080p title: Barbecue year: 2014 ? Underworld Quadrilogie VO+VFF+VFQ 1080p HDlight.x264~Tonyk~Monde Infernal : options: -n language: - fr - vo other: HDLight screenSize: 1080p title: Underworld Quadrilogie videoCodec: h264 ? A Bout Portant (The Killers).PAL.Multi.DVD-R-KZ : options: -n format: DVD language: mul releaseGroup: KZ title: A Bout Portant ? "Mise à Sac (Alain Cavalier, 1967) [Vhs.Rip.Vff]" : options: -n format: VHS language: fr title: "Mise à Sac" year: 1967 ? A Bout Portant (The Killers).PAL.Multi.DVD-R-KZ : options: -n format: DVD other: PAL language: mul releaseGroup: KZ title: A Bout Portant ? Youth.In.Revolt.(Be.Bad).2009.MULTI.1080p.LAME3*92-MEDIOZZ : options: -n audioCodec: MP3 language: mul releaseGroup: MEDIOZZ screenSize: 1080p title: Youth In Revolt year: 2009 ? La Defense Lincoln (The Lincoln Lawyer) 2011 [DVDRIP][Vostfr] : options: -n format: DVD subtitleLanguage: fr title: La Defense Lincoln year: 2011 ? '[h265 - HEVC] Fight Club French 1080p DTS.' : options: -n audioCodec: DTS language: fr screenSize: 1080p title: Fight Club videoCodec: h265 ? Love Gourou (Mike Myers) - FR : options: -n language: fr title: Love Gourou ? '[h265 - hevc] transformers 2 1080p french ac3 6ch.' : options: -n audioChannels: '5.1' audioCodec: AC3 language: fr screenSize: 1080p title: transformers 2 videoCodec: h265 ? 1.Angry.Man.1957.mkv : title: 1 Angry Man year: 1957 ? 12.Angry.Men.1957.mkv : title: 12 Angry Men year: 1957 ? 123.Angry.Men.1957.mkv : title: 123 Angry Men year: 1957 ? "Looney Tunes 1444x866 Porky's Last Stand.mkv" : screenSize: 1444x866 title: Looney Tunes guessit-0.11.0/guessit/test/test_hashes.py0000664000175000017500000000365612534564763021427 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from __future__ import absolute_import, division, print_function, unicode_literals from guessit.test.guessittest import * class TestHashes(TestGuessit): def test_hashes(self): hashes = ( ('hash_mpc', '1MB', u'8542ad406c15c8bd'), # TODO: Check if this value is valid ('hash_ed2k', '1MB', u'ed2k://|file|1MB|1048576|AA3CC5552A9931A76B61A41D306735F7|/'), # TODO: Check if this value is valid ('hash_md5', '1MB', u'5d8dcbca8d8ac21766f28797d6c3954c'), ('hash_sha1', '1MB', u'51d2b8f3248d7ee495b7750c8da5aa3b3819de9d'), ('hash_md5', 'dummy.srt', u'64de6b5893cac24456c46a935ef9c359'), ('hash_sha1', 'dummy.srt', u'a703fc0fa4518080505809bf562c6fc6f7b3c98c') ) for hash_type, filename, expected_value in hashes: guess = guess_file_info(file_in_same_dir(__file__, filename), hash_type) computed_value = guess.get(hash_type) assert expected_value == guess.get(hash_type), \ "Invalid %s for %s: %s != %s" % (hash_type, filename, computed_value, expected_value) guessit-0.11.0/PKG-INFO0000664000175000017500000006001612572402126015151 0ustar toilaltoilal00000000000000Metadata-Version: 1.1 Name: guessit Version: 0.11.0 Summary: GuessIt - a library for guessing information from video files. Home-page: http://guessit.readthedocs.org/ Author: Nicolas Wack Author-email: wackou@gmail.com License: LGPLv3 Download-URL: https://pypi.python.org/packages/source/g/guessit/guessit-0.11.0.tar.gz Description: GuessIt ======= .. image:: http://img.shields.io/pypi/v/guessit.svg :target: https://pypi.python.org/pypi/guessit :alt: Latest Version .. image:: http://img.shields.io/badge/license-LGPLv3-blue.svg :target: https://pypi.python.org/pypi/guessit :alt: License .. image:: http://img.shields.io/travis/wackou/guessit.svg :target: http://travis-ci.org/wackou/guessit :alt: Build Status .. image:: http://img.shields.io/coveralls/wackou/guessit.svg :target: https://coveralls.io/r/wackou/guessit?branch=master :alt: Coveralls GuessIt is a python library that extracts as much information as possible from a video file. It has a very powerful filename matcher that allows to guess a lot of metadata from a video using its filename only. This matcher works with both movies and tv shows episodes. For example, GuessIt can do the following:: $ guessit "Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi" For: Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi GuessIt found: { [1.00] "mimetype": "video/x-msvideo", [0.80] "episodeNumber": 3, [0.80] "videoCodec": "XviD", [1.00] "container": "avi", [1.00] "format": "HDTV", [0.70] "series": "Treme", [0.50] "title": "Right Place, Wrong Time", [0.80] "releaseGroup": "NoTV", [0.80] "season": 1, [1.00] "type": "episode" } Install ------- Installing GuessIt is simple with `pip `_:: $ pip install guessit or, with `easy_install `_:: $ easy_install guessit But, you really `shouldn't do that `_. You can now launch a demo:: $ guessit -d and guess your own filename:: $ guessit "Breaking.Bad.S05E08.720p.MP4.BDRip.[KoTuWa].mkv" For: Breaking.Bad.S05E08.720p.MP4.BDRip.[KoTuWa].mkv GuessIt found: { [1.00] "mimetype": "video/x-matroska", [1.00] "episodeNumber": 8, [0.30] "container": "mkv", [1.00] "format": "BluRay", [0.70] "series": "Breaking Bad", [1.00] "releaseGroup": "KoTuWa", [1.00] "screenSize": "720p", [1.00] "season": 5, [1.00] "type": "episode" } Filename matcher ---------------- The filename matcher is based on pattern matching and is able to recognize many properties from the filename, like ``title``, ``year``, ``series``, ``episodeNumber``, ``seasonNumber``, ``videoCodec``, ``screenSize``, ``language``. Guessed values are cleaned up and given in a readable format which may not match exactly the raw filename. The full list of available properties can be seen in the `main documentation `_. Other features -------------- GuessIt also allows you to compute a whole lot of hashes from a file, namely all the ones you can find in the hashlib python module (md5, sha1, ...), but also the Media Player Classic hash that is used (amongst others) by OpenSubtitles and SMPlayer, as well as the ed2k hash. If you have the 'guess-language' python package installed, GuessIt can also analyze a subtitle file's contents and detect which language it is written in. If you have the 'enzyme' python package installed, GuessIt can also detect the properties from the actual video file metadata. Usage ----- guessit can be use from command line:: $ guessit usage: guessit [-h] [-t TYPE] [-n] [-c] [-X DISABLED_TRANSFORMERS] [-v] [-P SHOW_PROPERTY] [-u] [-a] [-y] [-f INPUT_FILE] [-d] [-p] [-V] [-s] [--version] [-b] [-i INFO] [-S EXPECTED_SERIES] [-T EXPECTED_TITLE] [-Y] [-D] [-L ALLOWED_LANGUAGES] [-E] [-C ALLOWED_COUNTRIES] [-G EXPECTED_GROUP] [filename [filename ...]] positional arguments: filename Filename or release name to guess optional arguments: -h, --help show this help message and exit Naming: -t TYPE, --type TYPE The suggested file type: movie, episode. If undefined, type will be guessed. -n, --name-only Parse files as name only. Disable folder parsing, extension parsing, and file content analysis. -c, --split-camel Split camel case part of filename. -X DISABLED_TRANSFORMERS, --disabled-transformer DISABLED_TRANSFORMERS Transformer to disable (can be used multiple time) -S EXPECTED_SERIES, --expected-series EXPECTED_SERIES Expected series to parse (can be used multiple times) -T EXPECTED_TITLE, --expected-title EXPECTED_TITLE Expected title (can be used multiple times) -Y, --date-year-first If short date is found, consider the first digits as the year. -D, --date-day-first If short date is found, consider the second digits as the day. -L ALLOWED_LANGUAGES, --allowed-languages ALLOWED_LANGUAGES Allowed language (can be used multiple times) -E, --episode-prefer-number Guess "serie.213.avi" as the episodeNumber 213. Without this option, it will be guessed as season 2, episodeNumber 13 -C ALLOWED_COUNTRIES, --allowed-country ALLOWED_COUNTRIES Allowed country (can be used multiple times) -G EXPECTED_GROUP, --expected-group EXPECTED_GROUP Expected release group (can be used multiple times) Output: -v, --verbose Display debug output -P SHOW_PROPERTY, --show-property SHOW_PROPERTY Display the value of a single property (title, series, videoCodec, year, type ...) -u, --unidentified Display the unidentified parts. -a, --advanced Display advanced information for filename guesses, as json output -y, --yaml Display information for filename guesses as yaml output (like unit-test) -f INPUT_FILE, --input-file INPUT_FILE Read filenames from an input file. -d, --demo Run a few builtin tests instead of analyzing a file Information: -p, --properties Display properties that can be guessed. -V, --values Display property values that can be guessed. -s, --transformers Display transformers that can be used. --version Display the guessit version. guessit.io: -b, --bug Submit a wrong detection to the guessit.io service Other features: -i INFO, --info INFO The desired information type: filename, video, hash_mpc or a hash from python's hashlib module, such as hash_md5, hash_sha1, ...; or a list of any of them, comma-separated It can also be used as a python module:: >>> from guessit import guess_file_info >>> guess_file_info('Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi') {u'mimetype': 'video/x-msvideo', u'episodeNumber': 3, u'videoCodec': u'XviD', u'container': u'avi', u'format': u'HDTV', u'series': u'Treme', u'title': u'Right Place, Wrong Time', u'releaseGroup': u'NoTV', u'season': 1, u'type': u'episode'} Support ------- The project website for GuessIt is hosted at `ReadTheDocs `_. There you will also find the User guide and Developer documentation. This project is hosted on GitHub: ``_ Please report issues and/or feature requests via the `bug tracker `_. You can also report issues using the command-line tool:: $ guessit --bug "filename.that.fails.avi" Contribute ---------- GuessIt is under active development, and contributions are more than welcome! #. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. There is a Contributor Friendly tag for issues that should be ideal for people who are not very familiar with the codebase yet. #. Fork `the repository`_ on Github to start making your changes to the **master** branch (or branch off of it). #. Write a test which shows that the bug was fixed or that the feature works as expected. #. Send a pull request and bug the maintainer until it gets merged and published. :) .. _the repository: https://github.com/wackou/guessit License ------- GuessIt is licensed under the `LGPLv3 license `_. History ======= 0.11.0 (2015-09-04) ------------------- * Fixed year-season episodes with 'x' separator * Fixed name guessing when a subdirectory contains a number * Fixed possible IndexError in release_group plugin * Fixed infinite recursion when multiple languages from same node are ignored in the second pass * Added skip of language guess for 2-3 letters directories * Added exclusion of common words from title guessing * Added a higher confidence on filename over directories 0.10.4 (2015-08-19) ------------------- * Added ``LD``/``MD`` properties * Added better support for ``episodeList`` * Added more rules for filetype autodetection * Added support for ``episodeList`` on weak episode patterns * Added ``partList`` property (list for ``part`` property) * Added vob to supported file extensions * Added more ignore words to language detection * Added string options support for API methods (will be parsed like command-line) * Added better subtitle detection (prefix priority over suffix) * Fixed ``version`` property no detected when detached from ``episodeNumber`` * Fixed ``releaseGroup`` property no detected when prefixed by ``screenSize`` * Fixed single digit detected as an ``episodeNumber`` * Fixed an internal issue in matcher causing absolute and relative group spans confusion * Fixed an internal issue in properties container causing invalid ordering of found patterns * Fixed raw value for some properties (--advanced) * Use pytest as test runner * Remove support for python 2.6 0.10.3 (2015-04-04) ------------------- * Fix issues related to unicode encoding/decoding * Fix possible crashes in guess_video_rexps * Fix invalid guess result when crc32 contains 6 digits than can be parsed as a date 0.10.2 (2015-03-08) ------------------- * Use common words to resolve conflicts on strings * Bump babelfish version * Fix setuptools deprecation warning * Package argparse dependency only if python<2.7 0.10.1 (2015-01-05) ------------------- * Avoid word Stay to be recognized as AY subtitle * Fixed exception when no unidentified leaves remains * Avoid usage of deprecated EntryPoint.load() require argument * Fixed invalid raw data for some properties (title, series and maybe others) 0.10.0 (2014-12-27) ------------------- * Fixed exception when serie title starts with Ep * Fixed exception when trying to parse a full length country name * Removed deprecated optparse module, replaced by argparse 0.9.4 (2014-11-10) ------------------ * Fixed exception when filename contains multiple languages ISO codes * Fixed transformers initialization logging * Fixed possible exception in language transformer * Added more words to common english words 0.9.3 (2014-09-14) ------------------ * Added ``Preair`` and ``Remux`` to ``other`` property * Better detection of ``audioProfile`` = ``HD`` / ``HDMA`` for ``audioCodec`` = ``DTS`` * Better detection of ``format``` = ``BluRay`` (when followed by Rip) * Recognize ``RC`` as ``R5`` * Recognize ``WEB-HD```and ``ẀEB`` as ``WEB-DL`` 0.9.2 (2014-09-13) ------------------ * Added support of option registration on transformers * Better detection of ``releaseGroup`` when using ``expected-series`` or ``expected-title`` option * Better ``audioChannel`` = ``5.1`` / ``7.1`` guessing (``6ch``, ``8ch``) * Fixed usage not showing when invalid options were passed * Added ``PAL``, ``SECAM`` and ``NTSC`` to ``other`` possible values * Recognize DVD-9 and DVD-5 as ``format`` = ``DVD`` property 0.9.1 (2014-09-06) ------------------ * Added ``--unidentified`` option to display unidentified parts of the filename This option affects command line only - From API `unidentified` properties will always be grabbed regardless this settings * Better guessing of ``releaseGroup`` property * Added ``mHD`` and ``HDLight`` to ``other properties`` * Better guessing of ``format`` = ``DVD`` property (DVD-R pattern) * Some ``info`` logs changed to ``debug`` for quiet integration * Small fixes 0.9.0 (2014-09-05) ------------------ * Better auto-detection of anime episodes, containing a ``crc32`` or a digits ``episodeNumber``. * Better listing of options on ``guessit -h`` * Added ``--allowed-countries`` and ``--allowed-languages`` to avoid two or three letters words to be guessed as ``country`` or ``language`` * Added ``--disabled-transformers`` option to disable transformer plugin at runtime. * Added ``--episode-prefer-number`` option, for ``guess -t episode 'serie.123.avi'`` to return ``episodeNumber`` = ``123`` instead of ``season`` = ``1`` + ``episodeNumber`` = 23`` * Added ``--split-camel`` option (now disabled by default) * Added ``episodeCount`` and ``seasonCount`` properties (x-of-n notation) * Added ``--date-year-first``` and ``--date-day-first`` options * Added ``--expected-title``, ``--expected-series`` and ``--expected-groups`` to help finding values when those properties are known * Added ``10bit`` value to ``videoProfile`` * Added ``--show-property`` option to only show a single property * Added ``--input-file`` option to parse a list of * Added ``--version`` option * Added ``ass```to subtitle extensions * Added ``Fansub`` value for ``other`` property * Added more date formats support with ``dateutil`` dependency * Added customizable ``clean_function`` (API) * Added ``default_options`` (API) * Fixed ``--yaml`` option to support ``language`` and ``country`` * Fixed ``transformers.add_transformer()`` function (API) 0.8 (2014-07-06) ---------------- * New webservice that allows to use GuessIt just by sending a POST request to the http://guessit.io/guess url * Command-line util can now report bugs to the http://guessit.io/bugs service by specifying the ``-b`` or ``--bug`` flag * GuessIt can now use the Enzyme python package to detect metadata out of the actual video file metadata instead of the filename * Finished transition to ``babelfish.Language`` and ``babelfish.Country`` * New property: ``duration`` which returns the duration of the video in seconds This requires the Enzyme package to work * New property: ``fileSize`` which returns the size of the file in bytes * Renamed property ``special`` to ``episodeDetails`` * Added support for Python 3.4 * Optimization and bugfixes 0.7.1 (2014-03-03) ------------------ * New property "special": values can be trailer, pilot, unaired * New options for the guessit cmdline util: ``-y``, ``--yaml`` outputs the result in yaml format and ``-n``, ``--name-only`` analyzes the input as simple text (instead of filename) * Added properties formatters and validators * Removed support for python 3.2 * A healthy amount of code cleanup/refactoring and fixes :) 0.7 (2014-01-29) ---------------- * New plugin API that allows to register custom patterns / transformers * Uses Babelfish for language and country detection * Added Quality API to rate file quality from guessed property values * Better and more accurate overall detection * Added roman and word numeral detection * Added 'videoProfile' and 'audioProfile' property * Moved boolean properties to 'other' property value ('is3D' became 'other' = '3D') * Added more possible values for various properties. * Added command line option to list available properties and values * Fixes for Python3 support 0.6.2 (2013-11-08) ------------------ * Added support for nfo files * GuessIt can now output advanced information as json ('-a' on the command line) * Better language detection * Added new property: 'is3D' 0.6.1 (2013-09-18) ------------------ * New property "idNumber" that tries to identify a hash value or a serial number * The usual bugfixes 0.6 (2013-07-16) ---------------- * Better packaging: unittests and doc included in source tarball * Fixes everywhere: unicode, release group detection, language detection, ... * A few speed optimizations 0.5.4 (2013-02-11) ------------------ * guessit can be installed as a system wide script (thanks @dplarson) * Enhanced logging facilities * Fixes for episode number and country detection 0.5.3 (2012-11-01) ------------------ * GuessIt can now optionally act as a wrapper around the 'guess-language' python module, and thus provide detection of the natural language in which a body of text is written * Lots of fixes everywhere, mostly for properties and release group detection 0.5.2 (2012-10-02) ------------------ * Much improved auto-detection of filetype * Fixed some issues with the detection of release groups 0.5.1 (2012-09-23) ------------------ * now detects 'country' property; also detect 'year' property for series * more patterns and bugfixes 0.5 (2012-07-29) ---------------- * Python3 compatibility * the usual assortment of bugfixes 0.4.2 (2012-05-19) ------------------ * added Language.tmdb language code property for TheMovieDB * added ability to recognize list of episodes * bugfixes for Language.__nonzero__ and episode regexps 0.4.1 (2012-05-12) ------------------ * bugfixes for unicode, paths on Windows, autodetection, and language issues 0.4 (2012-04-28) ---------------- * much improved language detection, now also detect language variants * supports more video filetypes (thanks to Rob McMullen) 0.3.1 (2012-03-15) ------------------ * fixed package installation from PyPI * better imports for the transformations (thanks Diaoul!) * some small language fixes 0.3 (2012-03-12) ---------------- * fix to recognize 1080p format (thanks to Jonathan Lauwers) 0.3b2 (2012-03-02) ------------------ * fixed the package installation 0.3b1 (2012-03-01) ------------------ * refactored quite a bit, code is much cleaner now * fixed quite a few tests * re-vamped the documentation, wrote some more 0.2 (2011-05-27) ---------------- * new parser/matcher completely replaced the old one * quite a few more unittests and fixes 0.2b1 (2011-05-20) ------------------ * brand new parser/matcher that is much more flexible and powerful * lots of cleaning and a bunch of unittests 0.1 (2011-05-10) ---------------- * fixed a few minor issues & heuristics 0.1b2 (2011-03-12) ------------------ * Added PyPI trove classifiers * fixed version number in setup.py 0.1b1 (2011-03-12) ------------------ * first pre-release version; imported from Smewt with a few enhancements already in there. Keywords: smewt media video metadata python library Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Operating System :: OS Independent Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Multimedia Classifier: Topic :: Software Development :: Libraries :: Python Modules guessit-0.11.0/MANIFEST.in0000664000175000017500000000031612410027040015574 0ustar toilaltoilal00000000000000recursive-include docs *.rst *.py *.html *.png include HISTORY.rst graft docs/_themes include LICENSE include guessit/*.txt include guessit/test/*.yaml include guessit/test/*.txt include guessit/test/*.srt guessit-0.11.0/docs/0000775000175000017500000000000012572402126015001 5ustar toilaltoilal00000000000000guessit-0.11.0/docs/conf.py0000664000175000017500000001765112410027040016277 0ustar toilaltoilal00000000000000# -*- coding: utf-8 -*- # # GuessIt documentation build configuration file, created by # sphinx-quickstart on Tue Nov 29 15:48:01 2011. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) from guessit import __version__ # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'GuessIt' copyright = u'2014 Nicolas Wack' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = __version__ # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'flask_theme_support.FlaskyStyle' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. html_sidebars = { 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'], '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html'] } # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. html_show_sourcelink = False # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'GuessItdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'GuessIt.tex', u'GuessIt Documentation', u'Nicolas Wack', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'guessit', u'GuessIt Documentation', [u'Nicolas Wack'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'GuessIt', u'GuessIt Documentation', u'Nicolas Wack', 'GuessIt', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' sys.path.append(os.path.abspath('_themes')) html_theme_path = ['_themes'] html_theme = 'kr' guessit-0.11.0/docs/_themes/0000775000175000017500000000000012572402126016425 5ustar toilaltoilal00000000000000guessit-0.11.0/docs/_themes/flask_theme_support.pyc0000664000175000017500000000460412534565102023226 0ustar toilaltoilal00000000000000 . Tc@s|ddlmZddlmZmZmZmZmZmZm Z m Z m Z m Z m Z mZdefdYZdS(i(tStyle( tKeywordtNametCommenttStringtErrortNumbertOperatortGenerict Whitespacet PunctuationtOthertLiteralt FlaskyStylecBsZeZdZdZi<de6de6de6de6dej6de 6de j 6de j 6de j 6de j 6de j6de j6de6dej6d e6de6d ej6d ej6d ejj 6dej6dej 6d ej6dej6dej6dej6dej6dej6dej 6dej6dej6dej6dejj6dejj6dejj 6de!6de"6de"j#6de$6de$j%6de$j&6de$j'6de$j(6de$j)6de$j*6de$j+6de$j6de$j,6de$j-6de$j.6de/6de/j06de/j16de/j6de/j26de/j36d e/j46de/j56d e/j66de/j76de/j86Z9RS(s#f8f8f8tsunderline #f8f8f8s#a40000 border:#ef2929s#000000sitalic #8f5902tnoitalics bold #004461s#582800s bold #000000s#c4a000s#004461s#3465a4s#888s#ce5c00s bold #cc0000s#f57900s#990000s#4e9a06s#a40000sitalic #000000s#ef2929s bold #000080s#00A000s#745334s bold #800080s bold #a40000(:t__name__t __module__tbackground_colort default_styleR RR RtPreprocRtConstantt Declarationt NamespacetPseudotReservedtTypeRtWordR Rt AttributetBuiltintClasst DecoratortEntityt ExceptiontFunctiontPropertytLabeltTagtVariabletGlobaltInstanceRR tDateRtBackticktChartDoctDoubletEscapetHeredoctInterpoltRegextSingletSymbolRtDeletedtEmphtHeadingtInsertedtOutputtPrompttStrongt Subheadingt Tracebacktstyles(((sE/home/toilal/IdeaProjects/guessit/docs/_themes/flask_theme_support.pyR s~                                               N(tpygments.styleRtpygments.tokenRRRRRRRRR R R R R (((sE/home/toilal/IdeaProjects/guessit/docs/_themes/flask_theme_support.pytsRguessit-0.11.0/docs/_themes/kr/0000775000175000017500000000000012572402126017041 5ustar toilaltoilal00000000000000guessit-0.11.0/docs/_themes/kr/layout.html0000664000175000017500000000122012410027040021224 0ustar toilaltoilal00000000000000{%- extends "basic/layout.html" %} {%- block extrahead %} {{ super() }} {% if theme_touch_icon %} {% endif %} {% endblock %} {%- block relbar2 %}{% endblock %} {%- block footer %} {%- endblock %} guessit-0.11.0/docs/_themes/kr/theme.conf0000664000175000017500000000017212410027040020777 0ustar toilaltoilal00000000000000[theme] inherit = basic stylesheet = flasky.css pygments_style = flask_theme_support.FlaskyStyle [options] touch_icon = guessit-0.11.0/docs/_themes/kr/static/0000775000175000017500000000000012572402126020330 5ustar toilaltoilal00000000000000guessit-0.11.0/docs/_themes/kr/static/flasky.css_t0000664000175000017500000002135112410027040022645 0ustar toilaltoilal00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz and Nicolas Wack. * :license: Flask Design License, see LICENSE for details. */ {% set page_width = '1024px' %} {% set sidebar_width = '280px' %} @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Ubuntu', sans-serif; font-size: 17px; background-color: white; color: #000; margin: 0; padding: 0; } div.document { width: {{ page_width }}; margin: 30px auto 0 auto; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 0 0 0 {{ sidebar_width }}; } div.sphinxsidebar { width: {{ sidebar_width }}; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 0 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { width: {{ page_width }}; margin: 20px auto 30px auto; font-size: 14px; color: #888; text-align: right; } div.footer a { color: #888; } div.related { display: none; } div.sphinxsidebar a.dots { color: #444; text-decoration: none; border-bottom: 1px dotted #999; } div.sphinxsidebar a.dots:hover { border-bottom: 1px solid #999; } div.sphinxsidebar a.nodots { text-decoration: none; border-bottom: 0px; } div.sphinxsidebar a.nodots:hover { border-bottom: 0px; } div.sphinxsidebar { font-size: 14px; line-height: 1.5; } div.sphinxsidebarwrapper { padding: 18px 10px; } div.sphinxsidebarwrapper p.logo { padding: 0; margin: -10px 0 0 -20px; text-align: center; } div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: 'Ubuntu', sans-serif; color: #444; font-size: 24px; font-weight: normal; margin: 0 0 5px 0; padding: 0; } div.sphinxsidebar h4 { font-size: 20px; } div.sphinxsidebar h3 a { color: #444; } div.sphinxsidebar p.logo a, div.sphinxsidebar h3 a, div.sphinxsidebar p.logo a:hover, div.sphinxsidebar h3 a:hover { border: none; } div.sphinxsidebar p { color: #555; margin: 10px 0; } div.sphinxsidebar ul { margin: 10px 0; padding: 0; color: #000; } div.sphinxsidebar input { border: 1px solid #ccc; font-family: 'Ubuntu', sans-serif; font-size: 1em; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Ubuntu', sans-serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: #ddd; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition tt.xref, div.admonition a tt { border-bottom: 1px solid #fafafa; } dd div.admonition { margin-left: -60px; padding-left: 60px; } div.admonition p.admonition-title { font-family: 'Ubuntu', sans-serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight { background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Ubuntu Mono', sans-serif; font-size: 0.9em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; background: #fdfdfd; font-size: 0.9em; } table.footnote + table.footnote { margin-top: -15px; border-top: none; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td.label { width: 0px; padding: 0.3em 0 0.3em 0.5em; } table.footnote td { padding: 0.3em 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } blockquote { margin: 0 0 0 30px; padding: 0; } ul, ol { margin: 10px 0 10px 30px; padding: 0; } pre { background: #eee; padding: 7px 10px; margin: 15px -10px; line-height: 1.3em; } dl pre, blockquote pre, li pre { margin-left: -20px; padding-left: 20px; } dl dl pre { margin-left: -90px; padding-left: 90px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; border-bottom: 1px solid white; } a.reference { text-decoration: none; border-bottom: 1px dotted #004B6B; } a.reference:hover { border-bottom: 1px solid #6D4100; } a.footnote-reference { text-decoration: none; font-size: 0.7em; vertical-align: top; border-bottom: 1px dotted #004B6B; } a.footnote-reference:hover { border-bottom: 1px solid #6D4100; } a:hover tt { background: #EEE; } @media screen and (max-width: 870px) { div.sphinxsidebar { display: none; } div.document { width: 100%; } div.documentwrapper { margin-left: 0; margin-top: 0; margin-right: 0; margin-bottom: 0; } div.bodywrapper { margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; } ul { margin-left: 0; } .document { width: auto; } .footer { width: auto; } .bodywrapper { margin: 0; } .footer { width: auto; } .github { display: none; } } @media screen and (max-width: 875px) { body { margin: 0; padding: 20px 30px; } div.documentwrapper { float: none; background: white; } div.sphinxsidebar { display: block; float: none; width: 102.5%; margin: 50px -30px -20px -30px; padding: 10px 20px; background: #333; color: white; } div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, div.sphinxsidebar h3 a { color: white; } div.sphinxsidebar a { color: #aaa; } div.sphinxsidebar p.logo { display: none; } div.document { width: 100%; margin: 0; } div.related { display: block; margin: 0; padding: 10px 0 20px 0; } div.related ul, div.related ul li { margin: 0; padding: 0; } div.footer { display: none; } div.bodywrapper { margin: 0; } div.body { min-height: 0; padding: 0; } .rtd_doc_footer { display: none; } .document { width: auto; } .footer { width: auto; } .footer { width: auto; } .github { display: none; } } /* scrollbars */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment { display: block; height: 10px; } ::-webkit-scrollbar-button:vertical:increment { background-color: #fff; } ::-webkit-scrollbar-track-piece { background-color: #eee; -webkit-border-radius: 3px; } ::-webkit-scrollbar-thumb:vertical { height: 50px; background-color: #ccc; -webkit-border-radius: 3px; } ::-webkit-scrollbar-thumb:horizontal { width: 50px; background-color: #ccc; -webkit-border-radius: 3px; } /* misc. */ .revsys-inline { display: none!important; } guessit-0.11.0/docs/_themes/kr/relations.html0000664000175000017500000000111612410027040021713 0ustar toilaltoilal00000000000000

Related Topics

guessit-0.11.0/docs/_themes/kr_small/0000775000175000017500000000000012572402126020231 5ustar toilaltoilal00000000000000guessit-0.11.0/docs/_themes/kr_small/layout.html0000664000175000017500000000125312410027040022422 0ustar toilaltoilal00000000000000{% extends "basic/layout.html" %} {% block header %} {{ super() }} {% if pagename == 'index' %}
{% endif %} {% endblock %} {% block footer %} {% if pagename == 'index' %}
{% endif %} {% endblock %} {# do not display relbars #} {% block relbar1 %}{% endblock %} {% block relbar2 %} {% if theme_github_fork %} Fork me on GitHub {% endif %} {% endblock %} {% block sidebar1 %}{% endblock %} {% block sidebar2 %}{% endblock %} guessit-0.11.0/docs/_themes/kr_small/theme.conf0000664000175000017500000000027012410027040022166 0ustar toilaltoilal00000000000000[theme] inherit = basic stylesheet = flasky.css nosidebar = true pygments_style = flask_theme_support.FlaskyStyle [options] index_logo = '' index_logo_height = 120px github_fork = '' guessit-0.11.0/docs/_themes/kr_small/static/0000775000175000017500000000000012572402126021520 5ustar toilaltoilal00000000000000guessit-0.11.0/docs/_themes/kr_small/static/flasky.css_t0000664000175000017500000001100112410027040024024 0ustar toilaltoilal00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * Sphinx stylesheet -- flasky theme based on nature theme. * * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Georgia', serif; font-size: 17px; color: #000; background: white; margin: 0; padding: 0; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 40px auto 0 auto; width: 700px; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 30px 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { text-align: right; color: #888; padding: 10px; font-size: 14px; width: 650px; margin: 0 auto 40px auto; } div.footer a { color: #888; text-decoration: underline; } div.related { line-height: 32px; color: #888; } div.related ul { padding: 0 0 0 10px; } div.related a { color: #444; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body { padding-bottom: 40px; /* saved for footer */ } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } {% if theme_index_logo %} div.indexwrapper h1 { text-indent: -999999px; background: url({{ theme_index_logo }}) no-repeat center center; height: {{ theme_index_logo_height }}; } {% endif %} div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: white; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition p.admonition-title { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight{ background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } div.warning { background-color: #ffe4e4; border: 1px solid #f66; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.85em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td { padding: 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } pre { padding: 0; margin: 15px -30px; padding: 8px; line-height: 1.3em; padding: 7px 30px; background: #eee; border-radius: 2px; -moz-border-radius: 2px; -webkit-border-radius: 2px; } dl pre { margin-left: -60px; padding-left: 60px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; } a:hover tt { background: #EEE; } guessit-0.11.0/docs/_themes/.gitignore0000664000175000017500000000002612410027040020400 0ustar toilaltoilal00000000000000*.pyc *.pyo .DS_Store guessit-0.11.0/docs/_themes/flask_theme_support.py0000664000175000017500000001141312410027040023042 0ustar toilaltoilal00000000000000# flasky extensions. flasky pygments style based on tango style from pygments.style import Style from pygments.token import Keyword, Name, Comment, String, Error, \ Number, Operator, Generic, Whitespace, Punctuation, Other, Literal class FlaskyStyle(Style): background_color = "#f8f8f8" default_style = "" styles = { # No corresponding class for the following: #Text: "", # class: '' Whitespace: "underline #f8f8f8", # class: 'w' Error: "#a40000 border:#ef2929", # class: 'err' Other: "#000000", # class 'x' Comment: "italic #8f5902", # class: 'c' Comment.Preproc: "noitalic", # class: 'cp' Keyword: "bold #004461", # class: 'k' Keyword.Constant: "bold #004461", # class: 'kc' Keyword.Declaration: "bold #004461", # class: 'kd' Keyword.Namespace: "bold #004461", # class: 'kn' Keyword.Pseudo: "bold #004461", # class: 'kp' Keyword.Reserved: "bold #004461", # class: 'kr' Keyword.Type: "bold #004461", # class: 'kt' Operator: "#582800", # class: 'o' Operator.Word: "bold #004461", # class: 'ow' - like keywords Punctuation: "bold #000000", # class: 'p' # because special names such as Name.Class, Name.Function, etc. # are not recognized as such later in the parsing, we choose them # to look the same as ordinary variables. Name: "#000000", # class: 'n' Name.Attribute: "#c4a000", # class: 'na' - to be revised Name.Builtin: "#004461", # class: 'nb' Name.Builtin.Pseudo: "#3465a4", # class: 'bp' Name.Class: "#000000", # class: 'nc' - to be revised Name.Constant: "#000000", # class: 'no' - to be revised Name.Decorator: "#888", # class: 'nd' - to be revised Name.Entity: "#ce5c00", # class: 'ni' Name.Exception: "bold #cc0000", # class: 'ne' Name.Function: "#000000", # class: 'nf' Name.Property: "#000000", # class: 'py' Name.Label: "#f57900", # class: 'nl' Name.Namespace: "#000000", # class: 'nn' - to be revised Name.Other: "#000000", # class: 'nx' Name.Tag: "bold #004461", # class: 'nt' - like a keyword Name.Variable: "#000000", # class: 'nv' - to be revised Name.Variable.Class: "#000000", # class: 'vc' - to be revised Name.Variable.Global: "#000000", # class: 'vg' - to be revised Name.Variable.Instance: "#000000", # class: 'vi' - to be revised Number: "#990000", # class: 'm' Literal: "#000000", # class: 'l' Literal.Date: "#000000", # class: 'ld' String: "#4e9a06", # class: 's' String.Backtick: "#4e9a06", # class: 'sb' String.Char: "#4e9a06", # class: 'sc' String.Doc: "italic #8f5902", # class: 'sd' - like a comment String.Double: "#4e9a06", # class: 's2' String.Escape: "#4e9a06", # class: 'se' String.Heredoc: "#4e9a06", # class: 'sh' String.Interpol: "#4e9a06", # class: 'si' String.Other: "#4e9a06", # class: 'sx' String.Regex: "#4e9a06", # class: 'sr' String.Single: "#4e9a06", # class: 's1' String.Symbol: "#4e9a06", # class: 'ss' Generic: "#000000", # class: 'g' Generic.Deleted: "#a40000", # class: 'gd' Generic.Emph: "italic #000000", # class: 'ge' Generic.Error: "#ef2929", # class: 'gr' Generic.Heading: "bold #000080", # class: 'gh' Generic.Inserted: "#00A000", # class: 'gi' Generic.Output: "#888", # class: 'go' Generic.Prompt: "#745334", # class: 'gp' Generic.Strong: "bold #000000", # class: 'gs' Generic.Subheading: "bold #800080", # class: 'gu' Generic.Traceback: "bold #a40000", # class: 'gt' } guessit-0.11.0/docs/_themes/__pycache__/0000775000175000017500000000000012572402126020635 5ustar toilaltoilal00000000000000guessit-0.11.0/docs/_themes/__pycache__/flask_theme_support.cpython-34.pyc0000664000175000017500000000410112534565132027340 0ustar toilaltoilal00000000000000 . T @s|ddlmZddlmZmZmZmZmZmZm Z m Z m Z m Z m Z mZGdddeZdS))Style) KeywordNameCommentStringErrorNumberOperatorGeneric Whitespace PunctuationOtherLiteralc@sbeZdZdZdZi<de6de6de6de6dej 6de 6de j 6de j 6de j 6de j6de j6de j6d e6dej6d e6de6d ej6d ej6d ejj6dej6dej 6dej6dej6dej6dej6dej6dej6dej 6dej6dej6dej6dejj6dejj 6dejj!6de"6de#6de#j$6de%6de%j&6de%j'6de%j(6de%j)6de%j*6de%j+6de%j,6de%j6de%j-6de%j.6de%j/6de06de0j16de0j26de0j6de0j36de0j46de0j56de0j66d e0j76de0j86de0j96Z:dS) FlaskyStylez#f8f8f8zunderline #f8f8f8z#a40000 border:#ef2929z#000000zitalic #8f5902Znoitalicz bold #004461z#582800z bold #000000z#c4a000z#004461z#3465a4z#888z#ce5c00z bold #cc0000z#f57900z#990000z#4e9a06z#a40000zitalic #000000z#ef2929z bold #000080z#00A000z#745334z bold #800080z bold #a40000N);__name__ __module__ __qualname__Zbackground_colorZ default_styler rr rZPreprocrZConstantZ Declaration NamespaceZPseudoZReservedZTyper ZWordr r AttributeZBuiltinClassZ DecoratorZEntity ExceptionFunctionZPropertyZLabelTagZVariableGlobalInstancerrDaterZBacktickZCharZDocDoubleZEscapeZHeredocZInterpolZRegexSingleZSymbolr ZDeletedZEmphZHeadingZInsertedZOutputZPromptZStrongZ Subheading Tracebackstylesr!r!E/home/toilal/IdeaProjects/guessit/docs/_themes/flask_theme_support.pyrs~                                                rN)Zpygments.stylerZpygments.tokenrrrrrrr r r r r rrr!r!r!r"sRguessit-0.11.0/docs/_themes/README.rst0000664000175000017500000000133112410027040020077 0ustar toilaltoilal00000000000000krTheme Sphinx Style ==================== This repository contains sphinx styles Kenneth Reitz uses in most of his projects. It is a derivative of Mitsuhiko's themes for Flask and Flask related projects. To use this style in your Sphinx documentation, follow this guide: 1. put this folder as _themes into your docs folder. Alternatively you can also use git submodules to check out the contents there. 2. add this to your conf.py: :: sys.path.append(os.path.abspath('_themes')) html_theme_path = ['_themes'] html_theme = 'flask' The following themes exist: **kr** the standard flask documentation theme for large projects **kr_small** small one-page theme. Intended to be used by very small addon libraries. guessit-0.11.0/docs/_themes/LICENSE0000664000175000017500000000354512410027040017426 0ustar toilaltoilal00000000000000Modifications: Copyright (c) 2012 Nicolas Wack Copyright (c) 2011 Kenneth Reitz. Original Project: Copyright (c) 2010 by Armin Ronacher. Some rights reserved. Redistribution and use in source and binary forms of the theme, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. We kindly ask you to only use these themes in an unmodified manner just for Flask and Flask-related products, not for unrelated projects. If you like the visual style and want to use it for your own projects, please consider making some larger changes to the themes (such as changing font faces, sizes, colors or margins). THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. guessit-0.11.0/docs/index.rst0000664000175000017500000000202412410027040016625 0ustar toilaltoilal00000000000000GuessIt ======= Release v\ |version| (:ref:`Installation `) .. include:: presentation.rst User Guide ---------- This part of the documentation, which is mostly prose, shows how to use Guessit both from the command-line and as a python module which you can use in your own projects. .. toctree:: :maxdepth: 2 user/install user/commandline user/python Web Service API --------------- The guessit.io server also provides a free webservice that allows you to perform filename detection, even you don't have python installed (eg: you need to use it from an Android app, or NodeJS, etc.). You can look at the documentation for the web API here: ``_ Developer Guide --------------- If you want to contribute to the project, this part of the documentation is for you. .. toctree:: :maxdepth: 2 dev/internals You may also want to familiarize yourself with the following classes: .. toctree:: :maxdepth: 2 api/guess api/matchtree api/matcher .. include:: projectinfo.rst guessit-0.11.0/docs/user/0000775000175000017500000000000012572402126015757 5ustar toilaltoilal00000000000000guessit-0.11.0/docs/user/python.rst0000664000175000017500000000313112410027040020015 0ustar toilaltoilal00000000000000.. _python: Python module usage =================== The main entry points to the python module are the ``guess_file_info``, ``guess_movie_info`` and ``guess_episode_info``. The ``guess_file_info`` function will try to autodetect the type of the file, either movie, moviesubtitle, movieinfo, episode, episodesubtitle or episodeinfo. Pass them the filename and the desired information type: >>> import guessit >>> path = 'Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv' >>> guess = guessit.guess_movie_info(path, info=['filename']) >>> print type(guess) >>> print guess {'videoCodec': 'h264', 'container': 'mkv', 'format': 'BluRay', 'title': 'Dark City', 'releaseGroup': 'CHD', 'screenSize': '720p', 'year': 1998, 'type': 'movie', 'audioCodec': 'DTS'} >>> print guess.nice_string() { [1.00] "videoCodec": "h264", [1.00] "container": "mkv", [1.00] "format": "BluRay", [0.60] "title": "Dark City", [1.00] "releaseGroup": "CHD", [1.00] "screenSize": "720p", [1.00] "year": 1998, [1.00] "type": "movie", [1.00] "audioCodec": "DTS" } A ``Guess`` instance is a dictionary which has an associated confidence for each of the properties it has. A ``Guess`` instance is also a python dict instance, so you can use it wherever you would use a normal python dict. If you have the Enzyme python package installed, then the ``guess_video_metadata`` function is also available, which will return a guess with the properties from the video file metadata. guessit-0.11.0/docs/user/commandline.rst0000664000175000017500000001331112451746037021006 0ustar toilaltoilal00000000000000.. _commandline: Command-line usage ================== To have GuessIt try to guess some information from a filename, just run it as a command:: $ guessit "Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv" For: Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv GuessIt found: { [1.00] "videoCodec": "h264", [1.00] "container": "mkv", [1.00] "format": "BluRay", [0.60] "title": "Dark City", [1.00] "releaseGroup": "CHD", [1.00] "screenSize": "720p", [1.00] "year": 1998, [1.00] "type": "movie", [1.00] "audioCodec": "DTS" } The numbers between square brackets indicate the confidence in the value, so for instance in the previous example, GuessIt is sure that the videoCodec is h264, but only 60% confident that the title is 'Dark City'. You can use the ``-v`` or ``--verbose`` flag to have it display debug information. You can use the ``-p`` or ``-V`` flags to display the properties names or the multiple values they can take. You can also run a ``--demo`` mode which will run a few tests and display the results. By default, GuessIt will try to autodetect the type of file you are asking it to guess, movie or episode. If you want to force one of those, use the ``-t movie`` or ``-t episode`` flags. If input file is remote file or a release name with no folder and extension, you should use the ``-n`` or ``--name-only`` flag. It will disable folder and extension parsing, and any concrete file related analysis. Guessit also allows you to specify the type of information you want using the ``-i`` or ``--info`` flag:: $ guessit -i hash_md5,hash_sha1,hash_ed2k tests/dummy.srt For: tests/dummy.srt GuessIt found: { [1.00] "hash_ed2k": "ed2k://|file|dummy.srt|44|1CA0B9DED3473B926AA93A0A546138BB|/", [1.00] "hash_md5": "e781de9b94ba2753a8e2945b2c0a123d", [1.00] "hash_sha1": "bfd18e2f4e5d59775c2bc14d80f56971891ed620" } You can see the list of options that guessit.py accepts like that:: $ guessit --help usage: guessit [-h] [-t TYPE] [-n] [-c] [-X DISABLED_TRANSFORMERS] [-v] [-P SHOW_PROPERTY] [-u] [-a] [-y] [-f INPUT_FILE] [-d] [-p] [-V] [-s] [--version] [-b] [-i INFO] [-S EXPECTED_SERIES] [-T EXPECTED_TITLE] [-Y] [-D] [-L ALLOWED_LANGUAGES] [-E] [-C ALLOWED_COUNTRIES] [-G EXPECTED_GROUP] [filename [filename ...]] positional arguments: filename Filename or release name to guess optional arguments: -h, --help show this help message and exit Naming: -t TYPE, --type TYPE The suggested file type: movie, episode. If undefined, type will be guessed. -n, --name-only Parse files as name only. Disable folder parsing, extension parsing, and file content analysis. -c, --split-camel Split camel case part of filename. -X DISABLED_TRANSFORMERS, --disabled-transformer DISABLED_TRANSFORMERS Transformer to disable (can be used multiple time) -S EXPECTED_SERIES, --expected-series EXPECTED_SERIES Expected series to parse (can be used multiple times) -T EXPECTED_TITLE, --expected-title EXPECTED_TITLE Expected title (can be used multiple times) -Y, --date-year-first If short date is found, consider the first digits as the year. -D, --date-day-first If short date is found, consider the second digits as the day. -L ALLOWED_LANGUAGES, --allowed-languages ALLOWED_LANGUAGES Allowed language (can be used multiple times) -E, --episode-prefer-number Guess "serie.213.avi" as the episodeNumber 213. Without this option, it will be guessed as season 2, episodeNumber 13 -C ALLOWED_COUNTRIES, --allowed-country ALLOWED_COUNTRIES Allowed country (can be used multiple times) -G EXPECTED_GROUP, --expected-group EXPECTED_GROUP Expected release group (can be used multiple times) Output: -v, --verbose Display debug output -P SHOW_PROPERTY, --show-property SHOW_PROPERTY Display the value of a single property (title, series, videoCodec, year, type ...) -u, --unidentified Display the unidentified parts. -a, --advanced Display advanced information for filename guesses, as json output -y, --yaml Display information for filename guesses as yaml output (like unit-test) -f INPUT_FILE, --input-file INPUT_FILE Read filenames from an input file. -d, --demo Run a few builtin tests instead of analyzing a file Information: -p, --properties Display properties that can be guessed. -V, --values Display property values that can be guessed. -s, --transformers Display transformers that can be used. --version Display the guessit version. guessit.io: -b, --bug Submit a wrong detection to the guessit.io service Other features: -i INFO, --info INFO The desired information type: filename, video, hash_mpc or a hash from python's hashlib module, such as hash_md5, hash_sha1, ...; or a list of any of them, comma-separated guessit-0.11.0/docs/user/install.rst0000664000175000017500000000222612534564763020177 0ustar toilaltoilal00000000000000.. _install: Installation ============ This part of the documentation covers the installation of GuessIt. The first step to using any software package is getting it properly installed. Installing with Pip ------------------- Installing GuessIt is simple with `pip `_:: $ pip install guessit Getting the source code ----------------------- GuessIt is actively developed on GitHub, where the code is `always available `_. You can either clone the public repository:: $ git clone git://github.com/wackou/guessit.git Download the `tarball `_:: $ curl -L https://github.com/wackou/guessit/tarball/master -o guessit.tar.gz Or download the `zipball `_:: $ curl -L https://github.com/wackou/guessit/zipball/master -o guessit.zip Once you have a copy of the source, you can embed it in your Python package, install it into your site-packages folder like that:: $ python setup.py install or use it directly from the source folder for development:: $ python setup.py develop guessit-0.11.0/docs/user/properties.rst0000664000175000017500000001076612571103570020717 0ustar toilaltoilal00000000000000.. _properties: Properties ~~~~~~~~~~ Guessed values are cleaned up and given in a readable format which may not match exactly the raw filename. So, for instance, - ``DVDSCR`` will be guessed as ``format`` = ``DVD`` + ``other`` = ``Screener`` - ``1920x1080`` will be guessed as ``screenSize`` = ``1080p`` - ``DD5.1`` will be guessed as ``audioCodec`` = ``DolbyDigital`` + ``audioChannel`` = ``5.1`` Main properties ~~~~~~~~~~~~~~~ - **type** Type of the file. - ``unknown``, ``movie``, ``episode``, ``moviesubtitle``, ``episodesubtitle`` - **title** Title of movie or episode. - **container** Container of the file. - ``3g2``, ``wmv``, ``webm``, ``mp4``, ``avi``, ``mp4a``, ``mpeg``, ``sub``, ``mka``, ``m4v``, ``ts``, ``mkv``, ``ra``, ``rm``, ``wma``, ``ass``, ``mpg``, ``ram``, ``3gp``, ``ogv``, ``mov``, ``ogm``, ``asf``, ``divx``, ``ogg``, ``ssa``, ``qt``, ``idx``, ``nfo``, ``wav``, ``flv``, ``3gp2``, ``iso``, ``mk2``, ``srt`` - **date** Date found in filename. - **year** Year of movie (or episode). - **releaseGroup** Name of (non)scene group that released the file. - **website** Name of website contained in the filename. Episode properties ~~~~~~~~~~~~~~~~~~ - **series** Name of series. - **season** Season number. - **episodeNumber** Episode number. - **episodeList** List of episode numbers if several were found. - note: If several are found, ``episodeNumber`` is the first item of this list. - **seasonList** List of season numbers if several were found. - note: If several are found, ``seasonNumber`` is the first item of this list. - **episodeCount** Total number of episodes. - **seasonCount** Total number of seasons. - **episodeDetails** Some details about the episode. - ``Bonus`` ``Oav`` ``Ova`` ``Omake`` ``Extras`` ``Unaired`` ``Special`` ``Pilot`` - **episodeFormat** Episode format of the series. - ``Minisode`` - **part** Part number of the video. - **partList** List of part numbers if several were found. - note: If several are found, ``part`` is the first item of this list. - **version** Version of the episode. - In anime fansub scene, new versions are released with tag ``v[0-9]``. Video properties ~~~~~~~~~~~~~~~~ - **format** Format of the initial source - ``HDTV`` ``WEB-DL`` ``TV`` ``VOD`` ``BluRay`` ``DVD`` ``WEBRip`` ``Workprint`` ``Telecine`` ``VHS`` ``DVB`` ``Telesync`` ``HD-DVD`` ``PPV`` ``Cam`` - **screenSize** Resolution of video. - ``720p`` ``1080p`` ``1080i`` ``x`` ``4K`` ``360p`` ``368p`` ``480p`` ``576p`` ``900p`` - **videoCodec** Codec used for video. - ``h264`` ``h265`` ``DivX`` ``XviD`` ``Real`` ``Mpeg2`` - **videoProfile** Codec profile used for video. - ``8bit`` ``10bit`` ``HP`` ``BP`` ``MP`` ``XP`` ``Hi422P`` ``Hi444PP`` - **videoApi** API used for the video. - ``DXVA`` Audio properties ~~~~~~~~~~~~~~~~ - **audioChannels** Number of channels for audio. - ``1.0`` ``2.0`` ``5.1`` ``7.1`` - **audioCodec** Codec used for audio. - ``DTS`` ``TrueHD`` ``DolbyDigital`` ``AAC`` ``AC3`` ``MP3`` ``Flac`` - **audioProfile** The codec profile used for audio. - ``LC`` ``HQ`` ``HD`` ``HE`` ``HDMA`` Localization properties ~~~~~~~~~~~~~~~~~~~~~~~ - **Country** Country(ies) of content. Often found in series, ``Shameless (US)`` for instance. - ``[]`` (This class equals name and iso code) - **Language** Language(s) of the audio soundtrack. - ``[]`` (This class equals name and iso code) - **subtitleLanguage** Language(s) of the subtitles. - ``[]`` (This class equals name and iso code) Other properties ~~~~~~~~~~~~~~~~ - **bonusNumber** Bonus number. - **bonusTitle** Bonus title. - **cdNumber** CD number. - **cdNumberTotal** Total number of CD. - **crc32** CRC32 of the file. - **idNumber** Volume identifier (UUID). - **edition** Edition of the movie. - ``Special Edition``, ``Collector Edition``, ``Director's cut``, ``Criterion Edition``, ``Deluxe Edition`` - **filmNumber** Film number of this movie. - **filmSeries** Film series of this movie. - **other** Other property will appear under this property. - ``Fansub``, ``HR``, ``HQ``, ``Netflix``, ``Screener``, ``Unrated``, ``HD``, ``3D``, ``SyncFix``, ``Bonus``, ``WideScreen``, ``Fastsub``, ``R5``, ``AudioFix``, ``DDC``, ``Trailer``, ``Complete``, ``Limited``, ``Classic``, ``Proper``, ``DualAudio``, ``LiNE``, ``CC``, ``LD``, ``MD`` guessit-0.11.0/docs/dev/0000775000175000017500000000000012572402126015557 5ustar toilaltoilal00000000000000guessit-0.11.0/docs/dev/internals.rst0000664000175000017500000001720412410027040020301 0ustar toilaltoilal00000000000000.. _internals: Understanding the MatchTree --------------------------- The basic structure that the filename detection component uses is the ``MatchTree``. A ``MatchTree`` is a tree covering the filename, where each node represent a substring in the filename and can have a ``Guess`` associated with it that contains the information that has been guessed in this node. Nodes can be further split into subnodes until a proper split has been found. This makes it so that all the leaves concatenated will give you back the original filename. But enough theory, let's look at an example:: >>> path = 'Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv' >>> print guessit.IterativeMatcher(path).match_tree 000000 1111111111111111 2222222222222222222222222222222222222222222 333 000000 0000000000111111 0000000000111111222222222222222222222222222 000 011112 011112000000000000000000000000111 000000000000000000011112 0000000000111122222 0000111112 01112 Movies/__________(____)/Dark.City.(____).DC._____.____.___.____-___.___ tttttttttt yyyy yyyy fffff ssss aaa vvvv rrr ccc Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv The last line contains the filename, which you can use a reference. The previous line contains the type of property that has been found. The line before that contains the filename, where all the found groups have been blanked. Basically, what is left on this line are the leftover groups which could not be identified. The lines before that indicate the indices of the groups in the tree. For instance, the part of the filename 'BDRip' is the leaf with index ``(2, 2, 0, 0, 0, 1)`` (read from top to bottom), and its meaning is 'format' (as shown by the ``f``'s on the last-but-one line). What does the IterativeMatcher do? ---------------------------------- The goal of the :ref:`api/matcher` is to take a ``MatchTree`` which contains no information (yet!) at the beginning, and apply a succession of rules to try to guess parts of the filename. These rules are called transformations and work in-place on the tree, splitting into new leaves and updating the nodes's guesses when it finds some information. Let's look at what happens when matching the previous filename. Splitting into path components ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ First, we split the filename into folders + basename + extension This gives us the following tree, which has 4 leaves (from 0 to 3):: 000000 1111111111111111 2222222222222222222222222222222222222222222 333 Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv Splitting into explicit groups ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Then, we want to split each of those groups into "explicit" groups, i.e.: groups which are enclosed in parentheses, square brackets, curly braces, etc.:: 000000 1111111111111111 2222222222222222222222222222222222222222222 333 000000 0000000000111111 0000000000111111222222222222222222222222222 000 Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.___ ccc Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv As you can see, the containing folder has been split into 2 sub-groups, and the basename into 3 groups (separated by the year information). Note that we also got the information from the extension, as you can see above. Finding interesting patterns ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Now that this first split has been made, we can start finding some known patterns which we can identify in the filename. That is the main objective of the ``IterativeMatcher``, which will run a series of transformations which can identify groups in the filename and will annotate the corresponding nodes. For instance, the year:: 000000 1111111111111111 2222222222222222222222222222222222222222222 333 000000 0000000000111111 0000000000111111222222222222222222222222222 000 011112 011112 Movies/Dark City (____)/Dark.City.(____).DC.BDRip.720p.DTS.X264-CHD.___ yyyy yyyy ccc Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv Then, known properties usually found in video filenames:: 000000 1111111111111111 2222222222222222222222222222222222222222222 333 000000 0000000000111111 0000000000111111222222222222222222222222222 000 011112 011112000000000000000000000000111 000000000000000000011112 0000000000111122222 0000111112 01112 Movies/Dark City (____)/Dark.City.(____).DC._____.____.___.____-___.___ yyyy yyyy fffff ssss aaa vvvv rrr ccc Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv As you can see, this starts to branch pretty quickly, as each found group splits a leaf into further leaves. In this case, that gives us the year (1998), the format (BDRip), the screen size (720p), the video codec (x264) and the release group (CHD). Using positional rules to find the 'title' property ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Now that we found all the known patterns that we could, it is time to try to guess what is the title of the movie. This is done by looking at which groups in the filename are still unidentified, and trying to guess which one corresponds to the title by looking at their position:: 000000 1111111111111111 2222222222222222222222222222222222222222222 333 000000 0000000000111111 0000000000111111222222222222222222222222222 000 011112 011112000000000000000000000000111 000000000000000000011112 0000000000111122222 0000111112 01112 Movies/__________(____)/Dark.City.(____).DC._____.____.___.____-___.___ tttttttttt yyyy yyyy fffff ssss aaa vvvv rrr ccc Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv In this case, as the containing folder is composed of 2 groups, the second of which is the year, we can (usually) safely assume that the first one corresponds to the movie title. Merging all the results in a MatchTree to give a final Guess ------------------------------------------------------------ Once that we have matched as many groups as we could, the job is not done yet. Indeed, every leaf of the tree that we could identify contains the found property in its guess, but what we want at the end is to have a single ``Guess`` containing all the information. There are some simple strategies implemented to try to deal with conflicts and/or duplicate properties. In our example, 'year' appears twice, but as it has the same value, so it will be merged into a single 'year' property, but with a confidence that represents the combined confidence of both guesses. If the properties were conflicting, we would take the one with the highest confidence and lower it accordingly. Here:: >>> path = 'Movies/Dark City (1998)/Dark.City.(1998).DC.BDRip.720p.DTS.X264-CHD.mkv' >>> print guessit.guess_movie_info(path) {'videoCodec': 'h264', 'container': 'mkv', 'format': 'BluRay', 'title': 'Dark City', 'releaseGroup': 'CHD', 'screenSize': '720p', 'year': 1998, 'type': 'movie', 'audioCodec': 'DTS'} And that gives you your final guess! guessit-0.11.0/docs/_static/0000775000175000017500000000000012572402126016427 5ustar toilaltoilal00000000000000guessit-0.11.0/docs/_static/lgplv3-88x31.png0000664000175000017500000000364312410027040021130 0ustar toilaltoilal00000000000000PNG  IHDRXc sBIT|d pHYs33tEXtSoftwarewww.inkscape.org< IDATh}OU-KLڤHeMoFQTBYlFJ"!JBKlQ[C^5˒ _|oN߹?Mgv=y߹ysoP(TJGZ/ 6"P gmG%e5`?0zp `'a 8B匭\߃W>9ȷ |m9Wx,`'b >>=6t)>&Ni%>g %q{Vo㼶 ;6TrvDJ 67쨤Z`Z/e7۔4ej/l7a u};1䫲<Ҧ-oA hc/R}`WzM3 xT䁊&qfI~~Jy6X6Yy L> n 44ײ\S6v(}t6їYfBB0~nb}ACtg4Tm v\3<Lbd>ɦ<`0͍y1ؾº(pawn#ڷ A7lԐx_te9Qh* mR-O!MY~`;^0nFRVµk4q` ,KtTז9՚*ηx_ENlG+e++\L~7gG?7oGe2ʸz~ġnjg=1{)cڠÄf1OG48J[itT!Ĵr-GqJ"s_ϛD.~p|8m9 +Le9"OB)wvN{8z>V'=q\k\(jk_ܴb,)%7`E3SA    A   A   A   A   A IJ `gg =zða`\0qDƇ~dcҥ_EEZYYaDD֒[kjjgϞiMƘĉ寿R1`//`bbbsl| 0""BchtAAA#+++wԩJ7o*X4Pu|1o޼؎=JLb^l"gΜa<7>xmZ>}hի?J+gApJq=9AKƌuWVVQpxgQQk6ύ(ZP-N|IIIZ֘Q \gعs'kM1 \ ŗ}!\8u^Z).KKK[?f~%Bk "eT{g155 BmOOA$jرc "ɓUba[ZZB\\\#GTl?YbRHܘ5kTTT4_ ]Ƹ?))*`Hڱm68vXAAA_ 6l}*Iܹ{.| O~?cAAA`cc[n*{xxRHr:u*TUU$&&BvoEEL6 a„ J̆UϜ9Se3_6ommmˡt!'{M4 ˋqضo{QWǏEsݻw<'/BUn߾M$2tssk>wڵFl]mc6mXq9~ɯѣ&2čO4nڴ]n2[[rGq l=e嘒A GfJlܸh`ZxtN:PTT|Ӿ}{7o8pSۡk+l}3͙'@\gϞxHHHPi>|8b0-36ԩSA޽ood8t>|T %K@`` ]UK!!!pig=_ˆ#jIA XZZr>^{5Xx1词 z}xgAP4tO|-H$p HNNV@[[[6mDFF7=>yb޽Jk۶-ۗ2ƜXVVΝӧOCjj*\z"""`ܹбcGj|hwء.<<5kJbXeI`1nB 0;;bJ%K*۴E0$aI"##M6T0qDxʱe˖={ Cmm-)u?3l"11ٳT7"_V;O=m%QN%rlgj_ho>geeT*{\]]A.Í7 11֬YR1 TVV:cFC,Ê+q1f4hVG0-!I4VON6lmmuJGA" ,, D^CXXcmm RqNC|{[iNBt RRR秵 9FD6oz|СF&i%۳u0p@>|8A#&&|}}ףLxgdjJΖ>3vKm V\wnn޶r9իK/֮=֝Z=*'r'551Zm'$$ص(صkWi̙K,$@ 7 ⋬555*n߾J/\@$kjjz^vƀ>i333Q\KKKI$@\.g{֭:۔dػwoTQ$2eA_I\#@_d2 m__C`8tPXl83f TVVEPVTT'ZXX R1n___암 ̈́#نkKKKhX~mÔ8//1n+++|fΜhoƌ:W\\:t`9tP#5&&FŖ#fgggddhe+))N߾}neqE}}= И!T޸qCi޽;VTTf}Z.{xxpqq7n1cXՖ &VY *+]aPSSx)c`bСC[ sqp^|򰤤+**?~P(=^^^4/ _mb˗/gFttJZHCokqqqJ߿mllI& pxBzƄ3xVp1b{xɃɀپ4666*۲e ŋYc?~k++/@y/u5޶&&%%&L`Ѿ}{ߪJi,,,p9}||cڵyoH=z4:;;ם:u2 2'LMM ˗!++ Ν;㿸.?PmORUUzƎ eeelmm!66&Mdxy|DaժUr繹IIIpI֕؆meC|UUb߾}9?viVm> "ӧJiy[\\VVV*ٓ\vJͶ۷ϼ!ro'{?r ٱM}];wG}47n@777\HHۿMu3ҧO|Q*Z:Mϙ3Gܜҥ9N=fѣG8}ttrrBOOO/t~zoT6l`^t)>~h5P㮮x޽y9s&,&MKx5_M6 ???j.J;bժU AAAl2ѣJaȐ! nnnf/^GGGԩ< JU A4 HA$H$@ HA$H$@ HA$H$@ HA$H$@ HA$H$@ HaZ !;;!33򠠠*++ѣG }Э[7xۛ!( `ӧOM !;;̙:uiݻW^Eh"==5ӵ`׆8s 9*ҥKtF`]]Ν;pKe'MdUbu^0_FGGG#{{{3f sZҌm/A ĉj^߿?ر߿ XRR8o<9|pfqyp޽WފE'''f]~RfqqڵCYY'VUU񲜝 ̄H$b}e}ׯga1"<<\P`VV:88*>???No".`we$Dn:lmڴƞ={XӥϚ699YmRִᡒf˖-'v@\>Ĵ4pĈPK?ƠΟ?oRVUUv!޽ˋ,NN:i{q^}U?|PPAA` !^&fԩ*,,4e2oNo[r˚~ƌmZJm :t6݆ JJMcg.=a8pi|ȩD@sDMo ׯ_{-**JpZ5ꫯ Z(3qqԔ1((ѣGjϗܟz 066Vc`ZbS\'**Jcz+++H$ ֭[J~<)9u'3?!P7d2NÆ [Ld(Xۊzn>L wMϝ;7l7((Hc An2I(u_#5 x[[[x"<+VX,FHo޼~JJɡLԣGc999t䲖."#}Y6^uִΝ˩ 0;;ٳ*8ر#븾'٣2MEUݺu NWYYqR(S3f䖛'OFH .4% ր΋l򑉚^i,Y<@L93fXz> ̙,LWv.M^~!T*E\eeeQQQأGj\ې!C0//O ?ln۶1+V'{V;_ƍզٳ'[o߾XWW7ou֩n֭!ltIDATD'9v׬YéBEDG]xNNN믿jn;C_OOOLKKCDĀD" 鬒/%_ƌwy| yA''';vƒI.c^y2)^TAiZ>VVVݺuʯ~X]]lSfԨQ+O.)u^o@G^ 5v7?cioS{ƣGB=M6ҵkW\lV#// +yq ÇcJJNq[oChhƏyիWt-^fϞin,`SM^^^}v@> ܹǎ/Bnn.ܽ{*++sЫW/>4 hQB16>Q,޳b{$P1 'l>I$ DV(} h4Fsm !eIDATx,SKoe=xf<~ljU%iBhJ)TZ;ĂKX T-R%%!iةďx$>]{s%hG ILKIkjݽڼa_~϶_<#M@kjTUBZ(T jXA48ʝ?2¿ythE8Plj* g~wr 8Yp  'h6SEБ= {iKD0m8aFZEс1z0܇kд^nzV ZׅM8u98 %Am+ѓE"^>u,_,iF@FCTFӁe;`yKwnckyDh2_ gO,i_LvF]FErKf'FfȌdo(_dw_J{ btx2:,"hCX` 3{pp[,CǷ@= CG8"IDATx> j'99Y"R&4-))):M(Ȑ>>>nnnDTA@NFa^ǘ-RJ"11QUnn.(g*l!뤘3+Bx "Q;=LsHzGWtb3}zwg<*/c[Y 3 FzOTmO{G\֖<|Y㡴Uz7jvIG|Zg+>DqUօzo/&S_iwJ%>!J>M ge~ 0\QkuYeIENDB`guessit-0.11.0/docs/_templates/0000775000175000017500000000000012572402126017136 5ustar toilaltoilal00000000000000guessit-0.11.0/docs/_templates/sidebarintro.html0000664000175000017500000000470112410027040022500 0ustar toilaltoilal00000000000000

Build status License

Travis-CI Code coverage

GuessIt is a python library that tries to extract and/or guess as much information as possible from a file.

Useful Links

Donate

If you like GuessIt, please consider making a donation in bitcoins or in litecoins.



guessit-0.11.0/docs/_templates/sidebarlogo.html0000664000175000017500000000072712410027040022311 0ustar toilaltoilal00000000000000

GuessIt is a python library that tries to extract as much information as possible from a file.

guessit-0.11.0/docs/projectinfo.rst0000664000175000017500000000236212410027040020045 0ustar toilaltoilal00000000000000 Support ------- The project website for GuessIt is hosted at `ReadTheDocs `_. There you will also find the User guide and Developer documentation. This project is hosted on GitHub: ``_ Please report issues and/or feature requests via the `bug tracker `_. You can also report issues using the command-line tool:: $ guessit --bug "filename.that.fails.avi" Contribute ---------- GuessIt is under active development, and contributions are more than welcome! #. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. There is a Contributor Friendly tag for issues that should be ideal for people who are not very familiar with the codebase yet. #. Fork `the repository`_ on Github to start making your changes to the **master** branch (or branch off of it). #. Write a test which shows that the bug was fixed or that the feature works as expected. #. Send a pull request and bug the maintainer until it gets merged and published. :) .. _the repository: https://github.com/wackou/guessit License ------- GuessIt is licensed under the `LGPLv3 license `_. guessit-0.11.0/docs/api/0000775000175000017500000000000012572402126015552 5ustar toilaltoilal00000000000000guessit-0.11.0/docs/api/guess.rst0000664000175000017500000000031412410027040017415 0ustar toilaltoilal00000000000000Guess ===== .. module:: guessit.guess .. autoclass:: Guess :members: .. autofunction:: choose_int .. autofunction:: choose_string .. autofunction:: merge_similar_guesses .. autofunction:: merge_all guessit-0.11.0/docs/api/matcher.rst0000664000175000017500000000013512410027040017713 0ustar toilaltoilal00000000000000Matchers ======== .. module:: guessit.matcher .. autoclass:: IterativeMatcher :members: guessit-0.11.0/docs/api/matchtree.rst0000664000175000017500000000020612410027040020243 0ustar toilaltoilal00000000000000MatchTree ========= .. module:: guessit.matchtree .. autoclass:: BaseMatchTree :members: .. autoclass:: MatchTree :members: guessit-0.11.0/docs/presentation.rst0000664000175000017500000001400312534564763020262 0ustar toilaltoilal00000000000000 GuessIt is a python library that extracts as much information as possible from a video file. It has a very powerful filename matcher that allows to guess a lot of metadata from a video using its filename only. This matcher works with both movies and tv shows episodes. For example, GuessIt can do the following:: $ guessit "Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi" For: Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi GuessIt found: { [1.00] "mimetype": "video/x-msvideo", [0.80] "episodeNumber": 3, [0.80] "videoCodec": "XviD", [1.00] "container": "avi", [1.00] "format": "HDTV", [0.70] "series": "Treme", [0.50] "title": "Right Place, Wrong Time", [0.80] "releaseGroup": "NoTV", [0.80] "season": 1, [1.00] "type": "episode" } Filename matcher ---------------- The filename matcher is based on pattern matching and is able to recognize many properties from the filename, like ``title``, ``year``, ``series``, ``episodeNumber``, ``seasonNumber``, ``videoCodec``, ``screenSize``, ``language``. Guessed values are cleaned up and given in a readable format which may not match exactly the raw filename. The full list of available properties can be seen here: .. toctree:: :maxdepth: 2 user/properties Other features -------------- GuessIt also allows you to compute a whole lof of hashes from a file, namely all the ones you can find in the hashlib python module (md5, sha1, ...), but also the Media Player Classic hash that is used (amongst others) by OpenSubtitles and SMPlayer, as well as the ed2k hash. If you have the 'guess-language' python package installed, GuessIt can also analyze a subtitle file's contents and detect which language it is written in. If you have the 'enzyme' python package installed, GuessIt can also detect the properties from the actual video file metadata. Usage ----- GuessIt can be used from the command line:: $ guessit usage: guessit [-h] [-t TYPE] [-n] [-c] [-X DISABLED_TRANSFORMERS] [-v] [-P SHOW_PROPERTY] [-u] [-a] [-y] [-f INPUT_FILE] [-d] [-p] [-V] [-s] [--version] [-b] [-i INFO] [-S EXPECTED_SERIES] [-T EXPECTED_TITLE] [-Y] [-D] [-L ALLOWED_LANGUAGES] [-E] [-C ALLOWED_COUNTRIES] [-G EXPECTED_GROUP] [filename [filename ...]] positional arguments: filename Filename or release name to guess optional arguments: -h, --help show this help message and exit Naming: -t TYPE, --type TYPE The suggested file type: movie, episode. If undefined, type will be guessed. -n, --name-only Parse files as name only. Disable folder parsing, extension parsing, and file content analysis. -c, --split-camel Split camel case part of filename. -X DISABLED_TRANSFORMERS, --disabled-transformer DISABLED_TRANSFORMERS Transformer to disable (can be used multiple time) -S EXPECTED_SERIES, --expected-series EXPECTED_SERIES Expected series to parse (can be used multiple times) -T EXPECTED_TITLE, --expected-title EXPECTED_TITLE Expected title (can be used multiple times) -Y, --date-year-first If short date is found, consider the first digits as the year. -D, --date-day-first If short date is found, consider the second digits as the day. -L ALLOWED_LANGUAGES, --allowed-languages ALLOWED_LANGUAGES Allowed language (can be used multiple times) -E, --episode-prefer-number Guess "serie.213.avi" as the episodeNumber 213. Without this option, it will be guessed as season 2, episodeNumber 13 -C ALLOWED_COUNTRIES, --allowed-country ALLOWED_COUNTRIES Allowed country (can be used multiple times) -G EXPECTED_GROUP, --expected-group EXPECTED_GROUP Expected release group (can be used multiple times) Output: -v, --verbose Display debug output -P SHOW_PROPERTY, --show-property SHOW_PROPERTY Display the value of a single property (title, series, videoCodec, year, type ...) -u, --unidentified Display the unidentified parts. -a, --advanced Display advanced information for filename guesses, as json output -y, --yaml Display information for filename guesses as yaml output (like unit-test) -f INPUT_FILE, --input-file INPUT_FILE Read filenames from an input file. -d, --demo Run a few builtin tests instead of analyzing a file Information: -p, --properties Display properties that can be guessed. -V, --values Display property values that can be guessed. -s, --transformers Display transformers that can be used. --version Display the guessit version. guessit.io: -b, --bug Submit a wrong detection to the guessit.io service Other features: -i INFO, --info INFO The desired information type: filename, video, hash_mpc or a hash from python's hashlib module, such as hash_md5, hash_sha1, ...; or a list of any of them, comma-separated It can also be used as a python module:: >>> from guessit import guess_file_info >>> guess_file_info('Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi') {u'mimetype': 'video/x-msvideo', u'episodeNumber': 3, u'videoCodec': u'XviD', u'container': u'avi', u'format': u'HDTV', u'series': u'Treme', u'title': u'Right Place, Wrong Time', u'releaseGroup': u'NoTV', u'season': 1, u'type': u'episode'} guessit-0.11.0/setup.cfg0000664000175000017500000000017612572402126015676 0ustar toilaltoilal00000000000000[zest.releaser] python-file-with-version = guessit/__version__.py [egg_info] tag_build = tag_svn_revision = 0 tag_date = 0 guessit-0.11.0/README.rst0000664000175000017500000002127712571232631015552 0ustar toilaltoilal00000000000000GuessIt ======= .. image:: http://img.shields.io/pypi/v/guessit.svg :target: https://pypi.python.org/pypi/guessit :alt: Latest Version .. image:: http://img.shields.io/badge/license-LGPLv3-blue.svg :target: https://pypi.python.org/pypi/guessit :alt: License .. image:: http://img.shields.io/travis/wackou/guessit.svg :target: http://travis-ci.org/wackou/guessit :alt: Build Status .. image:: http://img.shields.io/coveralls/wackou/guessit.svg :target: https://coveralls.io/r/wackou/guessit?branch=master :alt: Coveralls GuessIt is a python library that extracts as much information as possible from a video file. It has a very powerful filename matcher that allows to guess a lot of metadata from a video using its filename only. This matcher works with both movies and tv shows episodes. For example, GuessIt can do the following:: $ guessit "Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi" For: Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi GuessIt found: { [1.00] "mimetype": "video/x-msvideo", [0.80] "episodeNumber": 3, [0.80] "videoCodec": "XviD", [1.00] "container": "avi", [1.00] "format": "HDTV", [0.70] "series": "Treme", [0.50] "title": "Right Place, Wrong Time", [0.80] "releaseGroup": "NoTV", [0.80] "season": 1, [1.00] "type": "episode" } Install ------- Installing GuessIt is simple with `pip `_:: $ pip install guessit or, with `easy_install `_:: $ easy_install guessit But, you really `shouldn't do that `_. You can now launch a demo:: $ guessit -d and guess your own filename:: $ guessit "Breaking.Bad.S05E08.720p.MP4.BDRip.[KoTuWa].mkv" For: Breaking.Bad.S05E08.720p.MP4.BDRip.[KoTuWa].mkv GuessIt found: { [1.00] "mimetype": "video/x-matroska", [1.00] "episodeNumber": 8, [0.30] "container": "mkv", [1.00] "format": "BluRay", [0.70] "series": "Breaking Bad", [1.00] "releaseGroup": "KoTuWa", [1.00] "screenSize": "720p", [1.00] "season": 5, [1.00] "type": "episode" } Filename matcher ---------------- The filename matcher is based on pattern matching and is able to recognize many properties from the filename, like ``title``, ``year``, ``series``, ``episodeNumber``, ``seasonNumber``, ``videoCodec``, ``screenSize``, ``language``. Guessed values are cleaned up and given in a readable format which may not match exactly the raw filename. The full list of available properties can be seen in the `main documentation `_. Other features -------------- GuessIt also allows you to compute a whole lot of hashes from a file, namely all the ones you can find in the hashlib python module (md5, sha1, ...), but also the Media Player Classic hash that is used (amongst others) by OpenSubtitles and SMPlayer, as well as the ed2k hash. If you have the 'guess-language' python package installed, GuessIt can also analyze a subtitle file's contents and detect which language it is written in. If you have the 'enzyme' python package installed, GuessIt can also detect the properties from the actual video file metadata. Usage ----- guessit can be use from command line:: $ guessit usage: guessit [-h] [-t TYPE] [-n] [-c] [-X DISABLED_TRANSFORMERS] [-v] [-P SHOW_PROPERTY] [-u] [-a] [-y] [-f INPUT_FILE] [-d] [-p] [-V] [-s] [--version] [-b] [-i INFO] [-S EXPECTED_SERIES] [-T EXPECTED_TITLE] [-Y] [-D] [-L ALLOWED_LANGUAGES] [-E] [-C ALLOWED_COUNTRIES] [-G EXPECTED_GROUP] [filename [filename ...]] positional arguments: filename Filename or release name to guess optional arguments: -h, --help show this help message and exit Naming: -t TYPE, --type TYPE The suggested file type: movie, episode. If undefined, type will be guessed. -n, --name-only Parse files as name only. Disable folder parsing, extension parsing, and file content analysis. -c, --split-camel Split camel case part of filename. -X DISABLED_TRANSFORMERS, --disabled-transformer DISABLED_TRANSFORMERS Transformer to disable (can be used multiple time) -S EXPECTED_SERIES, --expected-series EXPECTED_SERIES Expected series to parse (can be used multiple times) -T EXPECTED_TITLE, --expected-title EXPECTED_TITLE Expected title (can be used multiple times) -Y, --date-year-first If short date is found, consider the first digits as the year. -D, --date-day-first If short date is found, consider the second digits as the day. -L ALLOWED_LANGUAGES, --allowed-languages ALLOWED_LANGUAGES Allowed language (can be used multiple times) -E, --episode-prefer-number Guess "serie.213.avi" as the episodeNumber 213. Without this option, it will be guessed as season 2, episodeNumber 13 -C ALLOWED_COUNTRIES, --allowed-country ALLOWED_COUNTRIES Allowed country (can be used multiple times) -G EXPECTED_GROUP, --expected-group EXPECTED_GROUP Expected release group (can be used multiple times) Output: -v, --verbose Display debug output -P SHOW_PROPERTY, --show-property SHOW_PROPERTY Display the value of a single property (title, series, videoCodec, year, type ...) -u, --unidentified Display the unidentified parts. -a, --advanced Display advanced information for filename guesses, as json output -y, --yaml Display information for filename guesses as yaml output (like unit-test) -f INPUT_FILE, --input-file INPUT_FILE Read filenames from an input file. -d, --demo Run a few builtin tests instead of analyzing a file Information: -p, --properties Display properties that can be guessed. -V, --values Display property values that can be guessed. -s, --transformers Display transformers that can be used. --version Display the guessit version. guessit.io: -b, --bug Submit a wrong detection to the guessit.io service Other features: -i INFO, --info INFO The desired information type: filename, video, hash_mpc or a hash from python's hashlib module, such as hash_md5, hash_sha1, ...; or a list of any of them, comma-separated It can also be used as a python module:: >>> from guessit import guess_file_info >>> guess_file_info('Treme.1x03.Right.Place,.Wrong.Time.HDTV.XviD-NoTV.avi') {u'mimetype': 'video/x-msvideo', u'episodeNumber': 3, u'videoCodec': u'XviD', u'container': u'avi', u'format': u'HDTV', u'series': u'Treme', u'title': u'Right Place, Wrong Time', u'releaseGroup': u'NoTV', u'season': 1, u'type': u'episode'} Support ------- The project website for GuessIt is hosted at `ReadTheDocs `_. There you will also find the User guide and Developer documentation. This project is hosted on GitHub: ``_ Please report issues and/or feature requests via the `bug tracker `_. You can also report issues using the command-line tool:: $ guessit --bug "filename.that.fails.avi" Contribute ---------- GuessIt is under active development, and contributions are more than welcome! #. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. There is a Contributor Friendly tag for issues that should be ideal for people who are not very familiar with the codebase yet. #. Fork `the repository`_ on Github to start making your changes to the **master** branch (or branch off of it). #. Write a test which shows that the bug was fixed or that the feature works as expected. #. Send a pull request and bug the maintainer until it gets merged and published. :) .. _the repository: https://github.com/wackou/guessit License ------- GuessIt is licensed under the `LGPLv3 license `_. guessit-0.11.0/LICENSE0000664000175000017500000001674312410027040015056 0ustar toilaltoilal00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. guessit-0.11.0/setup.py0000664000175000017500000000771212534564763015610 0ustar toilaltoilal00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # GuessIt - A library for guessing information from filenames # Copyright (c) 2013 Nicolas Wack # # GuessIt is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GuessIt 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 # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . # from setuptools import setup, find_packages from setuptools.command.test import test as TestCommand import os import sys here = os.path.abspath(os.path.dirname(__file__)) README = open(os.path.join(here, 'README.rst')).read() HISTORY = open(os.path.join(here, 'HISTORY.rst')).read() install_requires = ['babelfish>=0.5.4', 'stevedore>=0.14', 'requests', 'python-dateutil>=2.1'] if sys.version_info < (2, 7): # argparse is part of the standard library in python 2.7+ install_requires.append('argparse') tests_require = ['pytest', 'PyYAML'] # Fabric not available (yet!) for python3 setup_requires = [] extras_require = {'language_detection': ['guess-language>=0.2'], 'video_metadata': ['enzyme']} entry_points = { 'console_scripts': [ 'guessit = guessit.__main__:main' ], } dependency_links = [] class PyTest(TestCommand): user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")] def initialize_options(self): TestCommand.initialize_options(self) self.pytest_args = [] def finalize_options(self): TestCommand.finalize_options(self) self.test_args = [] def run(self): import pytest errno = pytest.main(self.pytest_args) exit(errno) exec(open("guessit/__version__.py").read()) # load version without importing guessit args = dict(name='guessit', version=__version__, description='GuessIt - a library for guessing information from video files.', long_description=README + '\n\n' + HISTORY, # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers classifiers=['Development Status :: 5 - Production/Stable', 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 'Operating System :: OS Independent', 'Intended Audience :: Developers', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Topic :: Multimedia', 'Topic :: Software Development :: Libraries :: Python Modules' ], keywords='smewt media video metadata python library', author='Nicolas Wack', author_email='wackou@gmail.com', url='http://guessit.readthedocs.org/', download_url='https://pypi.python.org/packages/source/g/guessit/guessit-%s.tar.gz' % __version__, license='LGPLv3', packages=find_packages(), cmdclass={"test": PyTest}, include_package_data=True, install_requires=install_requires, setup_requires=setup_requires, tests_require=tests_require, entry_points=entry_points, extras_require=extras_require, dependency_links=dependency_links, test_suite='guessit.test', ) setup(**args)