unexist.dev

Changeset 343:2f1f980c070a

Added xbmc stuff
author unexist
date Mon, 31 Dec 2012 16:45:52 +0100
parents 7c6514780987
children f8d095807d42
files xbmc/plugin.video.youtube/LICENSE.txt xbmc/plugin.video.youtube/YouTubeCore.py xbmc/plugin.video.youtube/YouTubeFeeds.py xbmc/plugin.video.youtube/YouTubeLogin.py xbmc/plugin.video.youtube/YouTubeNavigation.py xbmc/plugin.video.youtube/YouTubePlayer.py xbmc/plugin.video.youtube/YouTubePlaylistControl.py xbmc/plugin.video.youtube/YouTubePluginSettings.py xbmc/plugin.video.youtube/YouTubeScraper.py xbmc/plugin.video.youtube/YouTubeStorage.py xbmc/plugin.video.youtube/YouTubeSubtitleControl.py xbmc/plugin.video.youtube/YouTubeUtils.py xbmc/plugin.video.youtube/addon.xml xbmc/plugin.video.youtube/changelog.txt xbmc/plugin.video.youtube/default.py xbmc/plugin.video.youtube/icon and thumbnail licensing.txt xbmc/plugin.video.youtube/icon.png xbmc/plugin.video.youtube/resources/language/Bulgarian/strings.xml xbmc/plugin.video.youtube/resources/language/English/strings.xml xbmc/plugin.video.youtube/resources/language/Russian/strings.xml xbmc/plugin.video.youtube/resources/language/language/Bulgarian/strings.xml xbmc/plugin.video.youtube/resources/language/language/English/strings.xml xbmc/plugin.video.youtube/resources/language/language/Russian/strings.xml xbmc/plugin.video.youtube/resources/settings.xml xbmc/plugin.video.youtube/storageserverdummy.py xbmc/plugin.video.youtube/thumbnails/add_user.png xbmc/plugin.video.youtube/thumbnails/contacts.png xbmc/plugin.video.youtube/thumbnails/default.png xbmc/plugin.video.youtube/thumbnails/discoball.png xbmc/plugin.video.youtube/thumbnails/downloads.png xbmc/plugin.video.youtube/thumbnails/explore.png xbmc/plugin.video.youtube/thumbnails/favorites.png xbmc/plugin.video.youtube/thumbnails/featured.png xbmc/plugin.video.youtube/thumbnails/feeds.png xbmc/plugin.video.youtube/thumbnails/history.png xbmc/plugin.video.youtube/thumbnails/liked.png xbmc/plugin.video.youtube/thumbnails/live.png xbmc/plugin.video.youtube/thumbnails/login.png xbmc/plugin.video.youtube/thumbnails/most.png xbmc/plugin.video.youtube/thumbnails/movies.png xbmc/plugin.video.youtube/thumbnails/music.png xbmc/plugin.video.youtube/thumbnails/newsubscriptions.png xbmc/plugin.video.youtube/thumbnails/next.png xbmc/plugin.video.youtube/thumbnails/playbyid.png xbmc/plugin.video.youtube/thumbnails/playlists.png xbmc/plugin.video.youtube/thumbnails/previous.png xbmc/plugin.video.youtube/thumbnails/recommended.png xbmc/plugin.video.youtube/thumbnails/search.png xbmc/plugin.video.youtube/thumbnails/settings.png xbmc/plugin.video.youtube/thumbnails/shows.png xbmc/plugin.video.youtube/thumbnails/subscriptions.png xbmc/plugin.video.youtube/thumbnails/top.png xbmc/plugin.video.youtube/thumbnails/trailers.png xbmc/plugin.video.youtube/thumbnails/uploads.png xbmc/plugin.video.youtube/thumbnails/user.png xbmc/plugin.video.youtube/thumbnails/watch_later.png xbmc/plugin.video.youtube/xbmcvfsdummy.py xbmc/script.module.parsedom/addon.xml xbmc/script.module.parsedom/changelog.txt xbmc/script.module.parsedom/lib/CommonFunctions.py xbmc/script.module.parsedom/lib/CommonFunctions.py.bak
diffstat 61 files changed, 8166 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/LICENSE.txt	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU 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
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/YouTubeCore.py	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,902 @@
+'''
+    YouTube plugin for XBMC
+    Copyright (C) 2010-2012 Tobias Ussing And Henrik Mosgaard Jensen
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import re
+import sys
+import time
+import socket
+import urllib
+import urllib2
+#import chardet
+
+try:
+    import simplejson as json
+except ImportError:
+    import json
+
+# ERRORCODES:
+# 200 = OK
+# 303 = See other (returned an error message)
+# 500 = uncaught error
+
+
+class url2request(urllib2.Request):
+    """Workaround for using DELETE with urllib2"""
+    def __init__(self, url, method, data=None, headers={}, origin_req_host=None, unverifiable=False):
+        self._method = method
+        urllib2.Request.__init__(self, url, data, headers, origin_req_host, unverifiable)
+
+    def get_method(self):
+        if self._method:
+            return self._method
+        else:
+            return urllib2.Request.get_method(self)
+
+
+class YouTubeCore():
+    APIKEY = "AI39si6hWF7uOkKh4B9OEAX-gK337xbwR9Vax-cdeF9CF9iNAcQftT8NVhEXaORRLHAmHxj6GjM-Prw04odK4FxACFfKkiH9lg"
+
+    #===============================================================================
+    # The time parameter restricts the search to videos uploaded within the specified time.
+    # Valid values for this parameter are today (1 day), this_week (7 days), this_month (1 month) and all_time.
+    # The default value for this parameter is all_time.
+    #
+    # This parameter is supported for search feeds as well as for the top_rated, top_favorites, most_viewed,
+    # most_popular, most_discussed and most_responded standard feeds.
+    #===============================================================================
+
+    urls = {}
+    urls['batch'] = "http://gdata.youtube.com/feeds/api/videos/batch"
+    urls['thumbnail'] = "http://i.ytimg.com/vi/%s/0.jpg"
+
+    def __init__(self):
+        self.settings = sys.modules["__main__"].settings
+        self.language = sys.modules["__main__"].language
+        self.plugin = sys.modules["__main__"].plugin
+        self.dbg = sys.modules["__main__"].dbg
+        self.storage = sys.modules["__main__"].storage
+        self.cache = sys.modules["__main__"].cache
+        self.login = sys.modules["__main__"].login
+        self.utils = sys.modules["__main__"].utils
+        self.common = sys.modules["__main__"].common
+        urllib2.install_opener(sys.modules["__main__"].opener)
+
+        timeout = [5, 10, 15, 20, 25][int(self.settings.getSetting("timeout"))]
+        if not timeout:
+            timeout = "15"
+        socket.setdefaulttimeout(float(timeout))
+        return None
+
+    def delete_favorite(self, params={}):
+        self.common.log("")
+        get = params.get
+
+        delete_url = "http://gdata.youtube.com/feeds/api/users/default/favorites"
+        delete_url += "/" + get('editid')
+        result = self._fetchPage({"link": delete_url, "api": "true", "login": "true", "auth": "true", "method": "DELETE"})
+        return (result["content"], result["status"])
+
+    def remove_contact(self, params={}):
+        self.common.log("")
+        get = params.get
+        delete_url = "http://gdata.youtube.com/feeds/api/users/default/contacts"
+        delete_url += "/" + get("contact")
+        result = self._fetchPage({"link": delete_url, "api": "true", "login": "true", "auth": "true", "method": "DELETE"})
+        return (result["content"], result["status"])
+
+    def remove_subscription(self, params={}):
+        self.common.log("")
+        get = params.get
+        delete_url = "http://gdata.youtube.com/feeds/api/users/default/subscriptions"
+        delete_url += "/" + get("editid")
+        result = self._fetchPage({"link": delete_url, "api": "true", "login": "true", "auth": "true", "method": "DELETE"})
+        return (result["content"], result["status"])
+
+    def add_contact(self, params={}):
+        self.common.log("")
+        get = params.get
+        url = "http://gdata.youtube.com/feeds/api/users/default/contacts"
+        add_request = '<?xml version="1.0" encoding="UTF-8"?> <entry xmlns="http://www.w3.org/2005/Atom" xmlns:yt="http://gdata.youtube.com/schemas/2007"><yt:username>%s</yt:username></entry>' % get("contact")
+        result = self._fetchPage({"link": url, "api": "true", "login": "true", "auth": "true", "request": add_request})
+        return (result["content"], result["status"])
+
+    def add_favorite(self, params={}):
+        get = params.get
+        url = "http://gdata.youtube.com/feeds/api/users/default/favorites"
+        add_request = '<?xml version="1.0" encoding="UTF-8"?><entry xmlns="http://www.w3.org/2005/Atom"><id>%s</id></entry>' % get("videoid")
+        result = self._fetchPage({"link": url, "api": "true", "login": "true", "auth": "true", "request": add_request})
+        return (result["content"], result["status"])
+
+    def add_subscription(self, params={}):
+        self.common.log("")
+        get = params.get
+        url = "http://gdata.youtube.com/feeds/api/users/default/subscriptions"
+        add_request = '<?xml version="1.0" encoding="UTF-8"?><entry xmlns="http://www.w3.org/2005/Atom" xmlns:yt="http://gdata.youtube.com/schemas/2007"> <category scheme="http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat" term="user"/><yt:username>%s</yt:username></entry>' % get("channel")
+        result = self._fetchPage({"link": url, "api": "true", "login": "true", "auth": "true", "request": add_request})
+        return (result["content"], result["status"])
+
+    def add_playlist(self, params={}):
+        get = params.get
+        url = "http://gdata.youtube.com/feeds/api/users/default/playlists"
+        add_request = '<?xml version="1.0" encoding="UTF-8"?><entry xmlns="http://www.w3.org/2005/Atom" xmlns:yt="http://gdata.youtube.com/schemas/2007"><title type="text">%s</title><summary>%s</summary></entry>' % (get("title"), get("summary"))
+        result = self._fetchPage({"link": url, "api": "true", "login": "true", "auth": "true", "request": add_request})
+        return (result["content"], result["status"])
+
+    def del_playlist(self, params={}):
+        self.common.log("")
+        get = params.get
+        url = "http://gdata.youtube.com/feeds/api/users/default/playlists/%s" % (get("playlist"))
+        result = self._fetchPage({"link": url, "api": "true", "login": "true", "auth": "true", "method": "DELETE"})
+        return (result["content"], result["status"])
+
+    def add_to_playlist(self, params={}):
+        get = params.get
+        self.common.log("")
+        url = "http://gdata.youtube.com/feeds/api/playlists/%s" % get("playlist")
+        add_request = '<?xml version="1.0" encoding="UTF-8"?><entry xmlns="http://www.w3.org/2005/Atom" xmlns:yt="http://gdata.youtube.com/schemas/2007"><id>%s</id></entry>' % get("videoid")
+        result = self._fetchPage({"link": url, "api": "true", "login": "true", "auth": "true", "request": add_request})
+        return (result["content"], result["status"])
+
+    def remove_from_playlist(self, params={}):
+        self.common.log("")
+        get = params.get
+        url = "http://gdata.youtube.com/feeds/api/playlists/%s/%s" % (get("playlist"), get("playlist_entry_id"))
+        result = self._fetchPage({"link": url, "api": "true", "login": "true", "auth": "true", "method": "DELETE"})
+        return (result["content"], result["status"])
+
+    def remove_from_watch_later(self, params={}):
+        self.common.log("")
+        get = params.get
+        url = "https://gdata.youtube.com/feeds/api/users/default/watch_later/%s" % get("playlist_entry_id")
+        result = self._fetchPage({"link": url, "api": "true", "login": "true", "auth": "true", "method": "DELETE"})
+        return (result["content"], result["status"])
+
+    def set_video_watched(self, params={}):
+        self.common.log("")
+        get = params.get
+        url = "https://gdata.youtube.com/feeds/api/users/default/watch_later/%s" % get("videoid")
+        result = self._fetchPage({"link": url, "api": "true", "login": "true", "auth": "true", "method": "DELETE"})
+        return (result["content"], result["status"])
+
+    def getCategoriesFolderInfo(self, xml, params={}):
+        self.common.log("")
+        self.common.log(xml)
+        entries = self.common.parseDOM(xml, "atom:category", ret=True)
+
+        folders = []
+        for node in entries:
+            folder = {}
+
+            if len(self.common.parseDOM(node, "yt:deprecated")):
+                continue
+            title = self.common.parseDOM(node, "atom:category", ret="label")[0]
+
+            if title:
+                folder['Title'] = self.common.replaceHTMLCodes(title)
+
+            folder['category'] = self.common.parseDOM(node, "atom:category", ret="term")[0]
+            folder["icon"] = "explore"
+            folder["thumbnail"] = "explore"
+            folder["feed"] = "feed_category"
+
+            folders.append(folder)
+
+        return folders
+
+    def getFolderInfo(self, xml, params={}):
+        get = params.get
+
+        self.common.log(xml)
+        entries = self.common.parseDOM(xml, "entry")
+        show_next = False
+
+        #find out if there are more pages
+        for link in self.common.parseDOM(xml, "link", ret="rel"):
+            if link == "next":
+                show_next = True
+                break
+
+        folders = []
+        for node in entries:
+            folder = {"published": "2008-07-05T19:56:35.000-07:00"}
+
+            if get("feed") != "feed_categories":
+                folder["login"] = "true"
+            title = self.common.parseDOM(node, "title")[0]
+            if title.find(": ") > 0:
+                title = title[title.find(": ") + 2:]
+                title = self.common.replaceHTMLCodes(title)
+                
+            folder['Title'] = title
+            for tmp in self.common.parseDOM(node, "published"):
+                folder['published'] = tmp
+
+            for entryid in self.common.parseDOM(node, "id"):
+                folder["editid"] = entryid[entryid.rfind(":") + 1:]
+
+            thumb = ""
+            if get("user_feed") == "contacts":
+                folder["thumbnail"] = "user"
+                folder["contact"] = self.common.parseDOM(node, 'yt:username')[0]
+                folder["store"] = "contact_options"
+                folder["folder"] = "true"
+
+            if get("user_feed") == "subscriptions":
+                folder["channel"] = self.common.parseDOM(node, 'yt:username')[0]
+
+            if get("user_feed") == "playlists":
+                folder['playlist'] = self.common.parseDOM(node, 'yt:playlistId')[0]
+                folder["user_feed"] = "playlist"
+
+            params["thumb"] = "true"
+            thumb = self.storage.retrieve(params, "thumbnail", folder)
+            if thumb:
+                folder["thumbnail"] = thumb
+
+            folders.append(folder)
+
+        if show_next:
+            self.utils.addNextFolder(folders, params)
+
+        return folders
+
+    def getBatchDetailsOverride(self, items, params={}):
+        videoids = []
+
+        for video in items:
+            for k, v in video.items():
+                if k == "videoid":
+                    videoids.append(v)
+
+        (ytobjects, status) = self.getBatchDetails(videoids, params)
+
+        for video in items:
+            videoid = video["videoid"]
+            for item in ytobjects:
+                if item['videoid'] == videoid:
+                    for k, v in video.items():
+                        item[k] = v
+
+        while len(items) > len(ytobjects):
+            ytobjects.append({'videoid': 'false'})
+
+        return (ytobjects, 200)
+
+    def getBatchDetailsThumbnails(self, items, params={}):
+        ytobjects = []
+        videoids = []
+
+        for (videoid, thumb) in items:
+            videoids.append(videoid)
+
+        (tempobjects, status) = self.getBatchDetails(videoids, params)
+
+        for i in range(0, len(items)):
+            (videoid, thumbnail) = items[i]
+            for item in tempobjects:
+                if item['videoid'] == videoid:
+                    item['thumbnail'] = thumbnail
+                    ytobjects.append(item)
+                    break
+
+        while len(items) > len(ytobjects):
+            ytobjects.append({'videoid': 'false'})
+
+        return (ytobjects, 200)
+
+    def getBatchDetails(self, items, params={}):
+        self.common.log("params: " + repr(params))
+        self.common.log("items: " + str(len(items)))
+        request_start = "<feed xmlns='http://www.w3.org/2005/Atom'\n xmlns:media='http://search.yahoo.com/mrss/'\n xmlns:batch='http://schemas.google.com/gdata/batch'\n xmlns:yt='http://gdata.youtube.com/schemas/2007'>\n <batch:operation type='query'/> \n"
+        request_end = "</feed>"
+
+        video_request = ""
+
+        ytobjects = []
+        status = 500
+        i = 1
+
+        temp_objs = self.cache.getMulti("videoidcache", items)
+
+        for index, videoid in enumerate(items):
+            if index < len(temp_objs):
+                if temp_objs[index]:
+                    ytobjects.append(eval(temp_objs[index]))
+                    continue
+            if videoid:
+                video_request += "<entry> \n <id>http://gdata.youtube.com/feeds/api/videos/" + videoid + "</id>\n</entry> \n"
+                if i == 50:
+                    final_request = request_start + video_request + request_end
+                    rstat = 403
+                    while rstat == 403:
+                        result = self._fetchPage({"link": "http://gdata.youtube.com/feeds/api/videos/batch", "api": "true", "request": final_request})
+                        rstat = self.common.parseDOM(result["content"], "batch:status", ret="code")
+                        if len(rstat) > 0:
+                            if int(rstat[len(rstat) - 1]) == 403:
+                                self.common.log("quota exceeded. Waiting 5 seconds. " + repr(rstat))
+                                rstat = 403
+                                time.sleep(5)
+
+                    temp = self.getVideoInfo(result["content"], params)
+                    ytobjects += temp
+                    video_request = ""
+                    i = 1
+                i += 1
+
+        if i > 1:
+            final_request = request_start + video_request + request_end
+            result = self._fetchPage({"link": "http://gdata.youtube.com/feeds/api/videos/batch", "api": "true", "request": final_request})
+
+            temp = self.getVideoInfo(result["content"], params)
+            ytobjects += temp
+
+            save_data = {}
+            for item in ytobjects:
+                save_data[item["videoid"]] = repr(item)
+            self.cache.setMulti("videoidcache", save_data)
+
+        if len(ytobjects) > 0:
+            status = 200
+
+        self.common.log("ytobjects: " + str(len(ytobjects)))
+
+        return (ytobjects, status)
+
+    #===============================================================================
+    #
+    # Internal functions to YouTubeCore.py
+    #
+    # Return should be value(True for bool functions), or False if failed.
+    #
+    # False MUST be handled properly in External functions
+    #
+    #===============================================================================
+
+    def _fetchPage(self, params={}):  # This does not handle cookie timeout for _httpLogin
+        if self.settings.getSetting("force_proxy") == "true" and self.settings.getSetting("proxy"):
+            params["proxy"] = self.settings.getSetting("proxy")
+
+        get = params.get
+        link = get("link")
+        ret_obj = {"status": 500, "content": "", "error": 0}
+        cookie = ""
+
+        if (get("url_data") or get("request") or get("hidden")) and False:
+            self.common.log("called for : " + repr(params['link']))
+        else:
+            self.common.log("called for : " + repr(params))
+
+        if get("auth", "false") == "true":
+            self.common.log("got auth")
+            if self._getAuth():
+                if link.find("?") > -1:
+                    link += "&oauth_token=" + self.settings.getSetting("oauth2_access_token")
+                else:
+                    link += "?oauth_token=" + self.settings.getSetting("oauth2_access_token")
+
+                self.common.log("updated link: " + link)
+            else:
+                self.common.log("couldn't get login token")
+
+        if not link or get("error", 0) > 2:
+            self.common.log("giving up")
+            return ret_obj
+
+        if get("url_data"):
+            request = urllib2.Request(link, urllib.urlencode(get("url_data")))
+            request.add_header('Content-Type', 'application/x-www-form-urlencoded')
+        elif get("request", "false") == "false":
+            if get("proxy"):
+                proxy = get("proxy")
+                link = proxy + urllib.quote(link)
+                self.common.log("got proxy: %s" % link)
+            else:
+                self.common.log("got default: %s" % link)
+
+            request = url2request(link, get("method", "GET"))
+        else:
+            self.common.log("got request")
+            request = urllib2.Request(link, get("request"))
+            request.add_header('X-GData-Client', "")
+            request.add_header('Content-Type', 'application/atom+xml')
+            request.add_header('Content-Length', str(len(get("request"))))
+
+        if get("proxy") or (self.settings.getSetting("proxy") != "" and link.find(self.settings.getSetting("proxy")) > -1):
+            proxy = self.settings.getSetting("proxy")
+            referer = proxy[:proxy.rfind("/")]
+            self.common.log("Added proxy refer: %s" % referer)
+
+            request.add_header('Referer', referer)
+
+        if get("api", "false") == "true":
+            self.common.log("got api")
+            request.add_header('GData-Version', '2.1')
+            request.add_header('X-GData-Key', 'key=' + self.APIKEY)
+        else:
+            request.add_header('User-Agent', self.common.USERAGENT)
+
+            if get("no-language-cookie", "false") == "false":
+                cookie += "PREF=f1=50000000&hl=en;"
+
+        if get("login", "false") == "true":
+            self.common.log("got login")
+            if (self.settings.getSetting("username") == "" or self.settings.getSetting("user_password") == ""):
+                self.common.log("_fetchPage, login required but no credentials provided")
+                ret_obj["status"] = 303
+                ret_obj["content"] = self.language(30622)
+                return ret_obj
+
+            # This should be a call to self.login._httpLogin()
+            if self.settings.getSetting("login_cookies") == "":
+                if isinstance(self.login, str):
+                    self.login = sys.modules["__main__"].login
+                self.login._httpLogin()
+
+            if self.settings.getSetting("login_cookies") != "":
+                tcookies = eval(self.settings.getSetting("login_cookies"))
+                self.common.log("Adding login cookies: " + repr(tcookies.keys()))
+                for key in tcookies.keys():
+                    cookie += "%s=%s;" % ( key, tcookies[key])
+
+        if get("referer", "false") != "false":
+            self.common.log("Added referer: %s" % get("referer"))
+            request.add_header('Referer', get("referer"))
+
+        try:
+            self.common.log("connecting to server... %s" % link )
+
+            if cookie:
+                self.common.log("Setting cookie: " + cookie)
+                request.add_header('Cookie', cookie)
+
+            con = urllib2.urlopen(request)
+
+            inputdata = con.read()
+            ret_obj["content"] = inputdata.decode("utf-8")
+            ret_obj["location"] = link
+
+            ret_obj["new_url"] = con.geturl()
+            ret_obj["header"] = str(con.info())
+            con.close()
+
+            self.common.log("Result: %s " % repr(ret_obj), 9)
+
+            # Return result if it isn't age restricted
+            if (ret_obj["content"].find("verify-actions") == -1 and ret_obj["content"].find("verify-age-actions") == -1):
+                self.common.log("done")
+                ret_obj["status"] = 200
+                return ret_obj
+            else:
+                self.common.log("Youtube requires you to verify your age to view this content: " + repr(params))
+
+        except urllib2.HTTPError, e:
+            cont = False
+            err = str(e)
+            msg = e.read()
+
+            self.common.log("HTTPError : " + err)
+            if e.code == 400 or True:
+                self.common.log("Unhandled HTTPError : [%s] %s " % (e.code, msg), 1)
+
+            if msg.find("<?xml") > -1:
+                acted = False
+
+                self.common.log("REPLACE THIS MINIDOM WITH PARSEDOM: " + repr(msg))
+                import xml.dom.minidom as minidom
+                dom = minidom.parseString(msg)
+                self.common.log(str(len(msg)))
+                domains = dom.getElementsByTagName("domain")
+                codes = dom.getElementsByTagName("code")
+                for domain in domains:
+                    self.common.log(repr(domain.firstChild.nodeValue), 5)
+                    if domain.firstChild.nodeValue == "yt:quota":
+                        self.common.log("Hit quota... sleeping for 100 seconds")
+                        time.sleep(100)
+                        acted = True
+
+                if not acted:
+                    for code in codes:
+                        self.common.log(repr(code.firstChild.nodeValue), 5)
+                        if code.firstChild.nodeValue == "too_many_recent_calls":
+                            self.common.log("Hit quota... sleeping for 10 seconds")
+                            time.sleep(10)
+                            acted = True
+
+            else:  # Legacy this.
+                if msg.find("yt:quota") > 1:
+                    self.common.log("Hit quota... sleeping for 10 seconds")
+                    time.sleep(10)
+                elif msg.find("too_many_recent_calls") > 1:
+                    self.common.log("Hit quota... sleeping for 10 seconds")
+                    time.sleep(10)
+                elif err.find("Token invalid") > -1:
+                    self.common.log("refreshing token")
+                    self._oRefreshToken()
+                elif err.find("User Rate Limit Exceeded") > -1:
+                    self.common.log("Hit limit... Sleeping for 10 seconds")
+                    time.sleep(10)
+                else:
+                    if e.fp:
+                        cont = e.fp.read()
+                        self.common.log("HTTPError - Headers: " + str(e.headers) + " - Content: " + cont)
+
+            params["error"] = get("error", 0) + 1
+            ret_obj = self._fetchPage(params)
+
+            if cont and ret_obj["content"] == "":
+                ret_obj["content"] = cont
+                ret_obj["status"] = 303
+
+            return ret_obj
+
+        except urllib2.URLError, e:
+            err = str(e)
+            self.common.log("URLError : " + err)
+            if err.find("SSL") > -1:
+                ret_obj["status"] = 303
+                ret_obj["content"] = self.language(30629)
+                ret_obj["error"] = 3  # Tell _findErrors that we have an error
+                return ret_obj
+
+            time.sleep(3)
+            params["error"] = get("error", 0) + 1
+            ret_obj = self._fetchPage(params)
+            return ret_obj
+
+        except socket.timeout:
+            self.common.log("Socket timeout")
+            return ret_obj
+
+    def _findErrors(self, ret, silent=False):
+        self.common.log("")
+
+        ## Couldn't find 2 factor or normal login
+        error = self.common.parseDOM(ret['content'], "div", attrs={"class": "errormsg"})
+        if len(error) == 0:
+            # An error in 2-factor
+            self.common.log("1")
+            error = self.common.parseDOM(ret['content'], "div", attrs={"class": "error smaller"})
+        if len(error) == 0:
+            self.common.log("2")
+            error = self.common.parseDOM(ret['content'], "div", attrs={"id": "unavailable-message"})
+        if len(error) == 0 and ret['content'].find("yt:quota") > -1:
+            self.common.log("3")
+            # Api quota
+            html = self.common.parseDOM(ret['content'], "error")
+            error = self.common.parseDOM(html, "code")
+
+        if len(error) == 0:  # Bad password for _httpLogin.
+            error = self.common.parseDOM(ret['content'], "span", attrs={"class": "errormsg"})
+
+            # Has a link. Lets remove that.
+            if len(error) == 1:
+                if error[0].find("<") > -1:
+                    error[0] = error[0][0:error[0].find("<")]
+
+        if len(error) == 0:
+            self.common.log("4")
+            error = self.common.parseDOM(ret['content'], "div", attrs={"id": "watch7-player-age-gate-content"})
+
+        if len(error) > 0:
+            self.common.log("Found error: " + repr(error))
+            error = self.common.stripTags(error[0])
+            self.common.log("Found error: " + repr(error))
+            if error.find("[") > -1:
+                error = error[0:error.find("[")]
+            error = urllib.unquote(error.replace("\n", " ").replace("  ", " ")).replace("&#39;", "'")
+            self.common.log("returning error : " + error.strip())
+            return error.strip()
+
+        # If no error was found. But fetchPage has an error level of 3+, return the fetchPage content.
+        if len(error) == 0 and ret["error"] >= 3:
+            self.common.log("Returning error from _fetchPage : " + repr(ret))
+            return ret["content"]
+
+        if not silent:
+            self.common.log("couldn't find any errors: " + repr(ret))
+
+        return False
+
+    def _oRefreshToken(self):
+        self.common.log("")
+        # Refresh token
+        if self.settings.getSetting("oauth2_refresh_token"):
+            url = "https://accounts.google.com/o/oauth2/token"
+            data = {"client_id": "208795275779.apps.googleusercontent.com",
+                "client_secret": "sZn1pllhAfyonULAWfoGKCfp",
+                "refresh_token": self.settings.getSetting("oauth2_refresh_token"),
+                "grant_type": "refresh_token"}
+            self.settings.setSetting("oauth2_access_token", "")
+            ret = self._fetchPage({"link": url, "no-language-cookie": "true", "url_data": data})
+            if ret["status"] == 200:
+                oauth = ""
+                try:
+                    oauth = json.loads(ret["content"])
+                except:
+                    self.common.log("Except: " + repr(ret))
+                    return False
+
+                self.common.log("- returning, got result a: " + repr(oauth))
+
+                self.settings.setSetting("oauth2_access_token", oauth["access_token"])
+                self.settings.setSetting("oauth2_expires_at", str(int(oauth["expires_in"]) + time.time()) )
+                self.common.log("Success")
+                return True
+            else:
+                self.common.log("Failure, Trying a clean login")
+                if isinstance(self.login, str):
+                    self.login = sys.modules["__main__"].login
+                self.login.login({"new": "true"})
+            return False
+
+        self.common.log("didn't even try")
+
+        return False
+
+    def performNewLogin(self):
+        self.common.log("")
+        if isinstance(self.login, str):
+            self.login = sys.modules["__main__"].login
+
+        (result, status) = self.login.login()
+
+        if status == 200:
+            self.common.log("returning new auth")
+            return self.settings.getSetting("oauth2_access_token")
+
+        self.common.log("failed because login failed")
+        return False
+
+    def refreshTokenIfNessecary(self):
+        now = time.time()
+
+        if self.settings.getSetting("oauth2_expires_at"):
+            expire_at = float(self.settings.getSetting("oauth2_expires_at"))
+        else:
+            expire_at = now
+
+        if expire_at <= now:
+            self.common.log("Oauth expired refreshing")
+            self._oRefreshToken()
+
+    def _getAuth(self):
+        self.common.log("")
+
+        self.refreshTokenIfNessecary()
+
+        auth = self.settings.getSetting("oauth2_access_token")
+        if (auth):
+            return auth
+
+        return self.performNewLogin()
+
+    def getVideoId(self, node):
+        videoid = "false"
+        for id in self.common.parseDOM(node, "yt:videoid"):
+            videoid = id
+
+        if videoid == "false":
+            for id in self.common.parseDOM(node, "content", ret="src"):
+                videoid = id
+                videoid = videoid[videoid.rfind("/") + 1:]
+
+        if videoid == "false":
+            for id in self.common.parseDOM(node, "link", ret="href"):
+                match = re.match('.*?v=(.*)\&.*', id)
+                if match:
+                    videoid = match.group(1)
+
+        return videoid
+
+    def getPlaylistId(self, node):
+        result = ""
+
+        for entryid in self.common.parseDOM(node, "id"):
+            entryid = entryid[entryid.rfind(":") + 1:]
+            result = entryid
+
+        return result
+
+    def videoIsUnavailable(self, node):
+        result = False
+
+        for state in self.common.parseDOM(node, "yt:state", ret=True):
+            # Ignore unplayable items.
+            if (state == 'deleted' or state == 'rejected'):
+                result = True
+
+            # Get reason for why we can't playback the file.
+            reason = self.common.parseDOM(node, "yt:state", ret="reasonCode")
+            value = self.common.parseDOM(node, "yt:state")
+
+            if not reason:
+                return result
+
+            if reason[0] in [ "private", 'requesterRegion']:
+                result = True
+            elif reason[0] != 'limitedSyndication':
+                self.common.log("removing video, reason: %s value: %s" % (reason[0], value[0]))
+                result = True
+
+        return result
+
+    def getVideoEditId(self, node):
+        result = ""
+        for edit_link in self.common.parseDOM(node, "link", ret=True):
+            for obj in self.common.parseDOM(edit_link, "link", attrs={"rel": "edit"}, ret="href"):
+                result = obj[obj.rfind('/') + 1:]
+        return result
+
+    def addNextPageLinkIfNecessary(self, params, xml, ytobjects):
+        show_next = False
+        # find out if there are more pages
+        for link in self.common.parseDOM(xml, "link", ret="rel"):
+            if link == "next":
+                show_next = True
+                break
+        if show_next:
+            self.utils.addNextFolder(ytobjects, params)
+
+    def updateVideoIdStatusInCache(self, pre_id, ytobjects):
+        self.common.log(pre_id)
+        save_data = {}
+        for item in ytobjects:
+            if "videoid" in item:
+                save_data[item["videoid"]] = repr(item)
+
+        self.cache.setMulti(pre_id, save_data)
+
+    def getVideoIdStatusFromCache(self, pre_id, ytobjects):
+        self.common.log(pre_id)
+        load_data = []
+        for item in ytobjects:
+            if "videoid" in item:
+                load_data.append(item["videoid"])
+
+        res = self.cache.getMulti(pre_id, load_data)
+        if len(res) != len(load_data):
+            self.common.log("Length mismatch:" + repr(res) + " - " + repr(load_data))
+        i = 0
+        for item in ytobjects:
+            if "videoid" in item:
+                if i < len(res):
+                    item["Overlay"] = res[i]
+                i += 1 # This can NOT be enumerated because there might be missing videoids
+        return ytobjects
+
+    def getVideoEntries(self, xml):
+        entries = self.common.parseDOM(xml, "entry")
+        if not entries:
+            entries = self.common.parseDOM(xml, "atom:entry")
+
+        return entries
+
+    def getVideoCreator(self, node):
+        result = ""
+
+        # media:credit is not set for favorites, playlists
+        for tmp in self.common.parseDOM(node, "media:credit"):
+            result = tmp
+        if result == "":
+            for tmp in self.common.parseDOM(node, "name"):
+                result = tmp
+
+        return result
+
+    def getVideoTitle(self, node):
+        result = ""
+        for tmp in self.common.parseDOM(node, "media:title"):
+            result = self.common.replaceHTMLCodes(tmp)
+        return result
+
+    def getVideoDuration(self, node):
+        result = 1
+
+        for tmp in self.common.parseDOM(node, "yt:duration", ret="seconds"):
+            tmp = int(tmp) / 60
+            if tmp:
+                result = tmp
+
+        return result
+
+    def getVideoUploadDate(self, node):
+        result = time.localtime()
+
+        for tmp in self.common.parseDOM(node, "published"):
+            result = time.strptime(tmp[:tmp.find(".")], "%Y-%m-%dT%H:%M:%S")
+
+        return result
+
+    def getViewCount(self, node):
+        result = 0
+
+        for tmp in self.common.parseDOM(node, "yt:statistics", ret="viewCount"):
+            result = int(tmp)
+
+        return result
+
+    def getVideoDescription(self, node, uploadDate, viewCount):
+        result = ""
+
+        for tmp in self.common.parseDOM(node, "media:description"):
+            result = self.common.replaceHTMLCodes(tmp)
+
+        infoString = "Date Uploaded: " + time.strftime("%Y-%m-%d %H:%M:%S", uploadDate) + ", "
+        infoString += "View count: " + str(viewCount)
+
+        result = infoString + "\n" + result
+
+        return result
+
+    def getVideoRating(self, node):
+        result = 0.0
+
+        for tmp in self.common.parseDOM(node, "gd:rating", ret="average"):
+            result = float(tmp)
+
+        return result
+
+    def getVideoGenre(self, node):
+        result = ""
+
+        for tmp in self.common.parseDOM(node, "media:category", ret="label"):
+            result = self.common.replaceHTMLCodes(tmp)
+
+        return result
+
+    def getVideoInfo(self, xml, params={}):
+        self.common.log("", 3)
+
+        entries = self.getVideoEntries(xml)
+
+        ytobjects = []
+        for node in entries:
+            video ={}
+
+            video["videoid"] = self.getVideoId(node)
+            video["playlist_entry_id"] = self.getPlaylistId(node)
+            video['editid'] = self.getVideoEditId(node)
+
+            if self.videoIsUnavailable(node):
+                self.common.log("Video is unavailable, removing from result.", 3)
+                video["videoid"] = "false"
+
+            video["Studio"] = self.getVideoCreator(node)
+            video["Title"] = self.getVideoTitle(node)
+            video["Duration"] = self.getVideoDuration(node)
+            video["Rating"] = self.getVideoRating(node)
+            video["Genre"] = self.getVideoGenre(node)
+
+            viewCount = self.getViewCount(node)
+            video["Count"] = viewCount
+            uploadDate = self.getVideoUploadDate(node)
+            video['Date'] = time.strftime("%d-%m-%Y", uploadDate)
+
+            video["Plot"] = self.getVideoDescription(node, uploadDate, viewCount)
+
+            video['thumbnail'] = self.urls["thumbnail"] % video['videoid']
+
+            ytobjects.append(video)
+
+        self.addNextPageLinkIfNecessary(params, xml, ytobjects)
+
+        self.updateVideoIdStatusInCache("videoidcache", ytobjects)
+        self.getVideoIdStatusFromCache("vidstatus-", ytobjects)
+
+        self.common.log("Done: " + str(len(ytobjects)),3)
+        return ytobjects
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/YouTubeFeeds.py	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,357 @@
+'''
+    YouTube plugin for XBMC
+    Copyright (C) 2010-2012 Tobias Ussing And Henrik Mosgaard Jensen
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import  urllib
+
+
+class YouTubeFeeds():
+    # YouTube General Feeds
+    urls = {}
+    urls['playlist'] = "http://gdata.youtube.com/feeds/api/playlists/%s"
+    urls['related'] = "http://gdata.youtube.com/feeds/api/videos/%s/related"
+    urls['search'] = "http://gdata.youtube.com/feeds/api/videos?q=%s&safeSearch=%s"
+
+    # YouTube User specific Feeds
+    urls['uploads'] = "http://gdata.youtube.com/feeds/api/users/%s/uploads"
+    urls['favorites'] = "http://gdata.youtube.com/feeds/api/users/%s/favorites"
+    urls['playlists'] = "http://gdata.youtube.com/feeds/api/users/%s/playlists"
+    urls['contacts'] = "http://gdata.youtube.com/feeds/api/users/default/contacts"
+    urls['subscriptions'] = "http://gdata.youtube.com/feeds/api/users/%s/subscriptions"
+    urls['newsubscriptions'] = "http://gdata.youtube.com/feeds/api/users/%s/newsubscriptionvideos"
+    urls["recommended"] = "http://gdata.youtube.com/feeds/api/users/default/recommendations"
+    urls['watch_later'] = "http://gdata.youtube.com/feeds/api/users/default/watch_later?v=2.1"
+    urls['watch_history'] = "http://gdata.youtube.com/feeds/api/users/default/watch_history?v=2"
+
+    # YouTube Standard feeds
+    urls['feed_categories'] = "http://gdata.youtube.com/schemas/2007/categories.cat"
+    urls['feed_category'] = "http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed_%s?v=2&time=%s"
+    urls['feed_rated'] = "http://gdata.youtube.com/feeds/api/standardfeeds/top_rated?time=%s"
+    urls['feed_favorites'] = "http://gdata.youtube.com/feeds/api/standardfeeds/top_favorites?time=%s"
+    urls['feed_viewed'] = "http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed?time=%s"
+    urls['feed_linked'] = "http://gdata.youtube.com/feeds/api/standardfeeds/most_popular?time=%s"
+    urls['feed_discussed'] = "http://gdata.youtube.com/feeds/api/standardfeeds/most_discussed?time=%s"
+    urls['feed_responded'] = "http://gdata.youtube.com/feeds/api/standardfeeds/most_responded?time=%s"
+    urls['feed_live'] = "http://gdata.youtube.com/feeds/api/charts/live/events/live_now"
+
+    # Wont work with time parameter
+    urls['feed_recent'] = "http://gdata.youtube.com/feeds/api/standardfeeds/most_recent"
+    urls['feed_featured'] = "http://gdata.youtube.com/feeds/api/standardfeeds/recently_featured"
+    urls['feed_trending'] = "http://gdata.youtube.com/feeds/api/standardfeeds/on_the_web"
+    urls['feed_shared'] = "http://gdata.youtube.com/feeds/api/standardfeeds/most_shared"
+
+    def __init__(self):
+        self.settings = sys.modules["__main__"].settings
+        self.language = sys.modules["__main__"].language
+        self.plugin = sys.modules["__main__"].plugin
+        self.dbg = sys.modules["__main__"].dbg
+        self.utils = sys.modules["__main__"].utils
+        self.storage = sys.modules["__main__"].storage
+        self.core = sys.modules["__main__"].core
+        self.pluginsettings = sys.modules["__main__"].pluginsettings
+        self.common = sys.modules["__main__"].common
+
+    def createUrl(self, params={}):
+        self.common.log("", 4)
+        get = params.get
+        time = "this_week"
+        per_page = self.pluginsettings.itemsPerPage()
+        region = self.pluginsettings.currentRegion()
+
+        page = get("page", "0")
+        start_index = per_page * int(page) + 1
+        url = ""
+
+        if (get("feed")):
+            url = self.urls[get("feed")]
+
+        if (get("user_feed")):
+            url = self.urls[get("user_feed")]
+
+        if get("search"):
+            url = self.urls["search"]
+            query = urllib.unquote_plus(get("search"))
+            safe_search = self.pluginsettings.safeSearchLevel()
+            url = url % (query, safe_search)
+            authors = self.settings.getSetting("stored_searches_author")
+            if len(authors) > 0:
+                try:
+                    authors = eval(authors)
+                    if query in authors:
+                        url += "&" + urllib.urlencode({'author': authors[query]})
+                except:
+                    self.common.log("Search - eval failed")
+
+        if (url.find("%s") > 0):
+            if (get("contact") and not (get("external") and get("channel"))):
+                url = url % get("contact")
+            elif (get("channel")):
+                url = url % get("channel")
+            elif (get("playlist")):
+                url = url % get("playlist")
+            elif (get("videoid") and not get("action") == "add_to_playlist"):
+                url = url % get("videoid")
+            elif (get("category")):
+                url = url % (get("category"), "today")
+            elif (url.find("time=") > 0):
+                url = url % time
+            else:
+                url = url % "default"
+
+        if (url.find("?") == -1):
+            url += "?"
+        else:
+            url += "&"
+
+        if not get("playlist") and not get("folder") and not get("action") == "play_all" and not get("action") == "add_to_playlist":
+            url += "start-index=" + repr(start_index) + "&max-results=" + repr(per_page)
+
+        if (url.find("standardfeeds") > 0 and region):
+            url = url.replace("/standardfeeds/", "/standardfeeds/" + region + "/")
+
+        url = url.replace(" ", "+")
+        self.common.log(url, 4)
+        return url
+
+    def list(self, params={}):
+        self.common.log("", 4)
+        get = params.get
+        result = {"content": "", "status": 303}
+
+        if get("folder"):
+            return self.listFolder(params)
+
+        if get("playlist"):
+            return self.listPlaylist(params)
+
+        if get("login") == "true":
+            if (not self.core._getAuth()):
+                self.common.log("Login required but auth wasn't set!")
+                return (self.language(30609), 303)
+
+        url = self.createUrl(params)
+
+        if url:
+            self.common.log(repr(url), 4)
+            result = self.core._fetchPage({"link": url, "auth": get("login"), "api": "true"})
+
+        if result["status"] != 200:
+            return (result["content"], result["status"])
+
+        videos = self.core.getVideoInfo(result["content"], params)
+
+        if len(videos) == 0:
+            return (videos, 303)
+
+        thumbnail = videos[0].get('thumbnail', "")
+
+        if thumbnail:
+            self.storage.store(params, thumbnail, "thumbnail")
+
+        self.common.log("Done", 4)
+        return (videos, 200)
+
+    def listPlaylist(self, params={}):
+        self.common.log("", 4)
+        get = params.get
+        page = int(get("page", "0"))
+        per_page = self.pluginsettings.itemsPerPage()
+        next = 'false'
+
+        videos = self.storage.retrieve(params)
+
+        if page != 0 and videos:
+            if (per_page * (page + 1) < len(videos)):
+                next = 'true'
+
+            videos = videos[(per_page * page):(per_page * (page + 1))]
+
+            (result, status) = self.core.getBatchDetailsOverride(videos, params)
+        else:
+            result = self.listAll(params)
+
+            if len(result) == 0:
+                return (result, 303)
+
+            videos = []
+            for video in result:
+                vget = video.get
+                item = {}
+                item["playlist_entry_id"] = vget("playlist_entry_id")
+                item["videoid"] = vget("videoid")
+                videos.append(item)
+
+            self.storage.store(params, videos)
+
+            thumbnail = result[0].get('thumbnail', "")
+            if (thumbnail):
+                self.storage.store(params, thumbnail, "thumbnail")
+
+            if (len(result) > 0 and get("fetch_all") != "true"):
+                if (per_page * (page + 1) < len(result)):
+                    next = 'true'
+
+                result = result[(per_page * page):(per_page * (page + 1))]
+
+        if next == "true":
+            self.utils.addNextFolder(result, params)
+
+        self.common.log(repr(result), 4)
+        return (result, 200)
+
+    def listFolder(self, params={}):
+        self.common.log("", 4)
+        get = params.get
+        result = []
+
+        if get("store"):
+            if get("store") == "contact_options":
+                return self.storage.getUserOptionFolder(params)
+            else:
+                return self.storage.getStoredSearches(params)
+
+        page = int(get("page", "0"))
+        per_page = self.pluginsettings.itemsPerPage()
+
+        if (page != 0):
+            result = self.storage.retrieve(params)
+
+        elif not get("page"):
+            if get("feed") == "feed_categories":
+                result = self.listCategories(params)
+            else:
+                result = self.listAll(params)
+
+            if len(result) == 0:
+                return (result, 303)
+
+            self.storage.store(params, result)
+
+        next = 'false'
+
+        if (len(result) > 0):
+            if (per_page * (page + 1) < len(result)):
+                next = 'true'
+        result = result[(per_page * page):(per_page * (page + 1))]
+
+        if get("user_feed") == "subscriptions":
+            for item in result:
+                viewmode = self.storage.retrieve(params, "viewmode", item)
+
+                if (get("external")):
+                    item["external"] = "true"
+                    item["contact"] = get("contact")
+
+                if (viewmode == "favorites"):
+                    item["user_feed"] = "favorites"
+                    item["view_mode"] = "subscriptions_uploads"
+                elif(viewmode == "playlists"):
+                    item["user_feed"] = "playlists"
+                    item["folder"] = "true"
+                    item["view_mode"] = "subscriptions_playlists"
+                else:
+                    item["user_feed"] = "uploads"
+                    item["view_mode"] = "subscriptions_favorites"
+
+        if next == "true":
+            self.utils.addNextFolder(result, params)
+
+        self.common.log(repr(result), 4)
+        return (result, 200)
+
+    def listCategories(self, params={}):
+        self.common.log("", 4)
+
+        url = self.createUrl(params)
+        ytobjects = []
+
+        result = self.core._fetchPage({"link": url})
+
+        if result["status"] == 200:
+            ytobjects = self.core.getCategoriesFolderInfo(result["content"], params)
+
+        if len(ytobjects) == 0:
+            return ytobjects
+
+        self.common.log(repr(ytobjects), 4)
+        return ytobjects
+
+    def listAll(self, params={}):
+        self.common.log("", 4)
+        get = params.get
+        result = {"content": "", "status": 303}
+
+        auth = "false"
+        if get("login") == "true":
+            auth = "true"
+            if (not self.core._getAuth()):
+                self.common.log("login required but auth wasn't set!")
+                return (self.language(30609), 303)
+
+        feed = self.createUrl(params)
+        index = 1
+        url = feed + "v=2.1&start-index=" + str(index) + "&max-results=" + repr(50)
+        url = url.replace(" ", "+")
+
+        ytobjects = []
+
+        result = self.core._fetchPage({"link": url, "auth": auth})
+
+        if result["status"] == 200:
+            if get("folder") == "true":
+                ytobjects = self.core.getFolderInfo(result["content"], params)
+            else:
+                ytobjects = self.core.getVideoInfo(result["content"], params)
+
+        if len(ytobjects) == 0:
+            return ytobjects
+
+        next = ytobjects[len(ytobjects) - 1].get("next", "false")
+        if next == "true":
+            ytobjects = ytobjects[:len(ytobjects) - 1]
+
+        while next == "true":
+            index += 50
+            url = feed + "start-index=" + str(index) + "&max-results=" + repr(50)
+            url = url.replace(" ", "+")
+            result = self.core._fetchPage({"link": url, "auth": "true"})
+
+            if result["status"] != 200:
+                break
+            temp_objects = []
+            if get("folder") == "true":
+                temp_objects = self.core.getFolderInfo(result["content"], params)
+            else:
+                temp_objects = self.core.getVideoInfo(result["content"], params)
+
+            if len(temp_objects) > 0:
+                next = temp_objects[len(temp_objects) - 1].get("next", "false")
+                if next == "true":
+                    temp_objects = temp_objects[:len(temp_objects) - 1]
+                ytobjects += temp_objects
+            else:
+                self.common.log("Didn't get any temp_objects. This should NOT happen")
+
+        if get("user_feed"):
+            if get("user_feed") != "playlist" and get("action") != "play_all":
+                ytobjects.sort(key=lambda item: item["Title"].lower(), reverse=False)
+            elif (self.storage.getReversePlaylistOrder(params)):
+                ytobjects.reverse()
+
+        self.common.log(repr(ytobjects), 4)
+        return ytobjects
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/YouTubeLogin.py	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,354 @@
+'''
+    YouTube plugin for XBMC
+    Copyright (C) 2010-2012 Tobias Ussing And Henrik Mosgaard Jensen
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import re
+import sys
+import time
+try: import simplejson as json
+except ImportError: import json
+
+# ERRORCODES:
+# 0 = Ignore
+# 200 = OK
+# 303 = See other (returned an error message)
+# 500 = uncaught error
+
+
+class YouTubeLogin():
+    APIKEY = "AI39si6hWF7uOkKh4B9OEAX-gK337xbwR9Vax-cdeF9CF9iNAcQftT8NVhEXaORRLHAmHxj6GjM-Prw04odK4FxACFfKkiH9lg"
+
+    urls = {}
+    urls[u"oauth_api_login"] = u"https://accounts.google.com/o/oauth2/auth?client_id=208795275779.apps.googleusercontent.com&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=http%3A%2F%2Fgdata.youtube.com&response_type=code"
+
+    def __init__(self):
+        self.xbmc = sys.modules["__main__"].xbmc
+
+        self.pluginsettings = sys.modules["__main__"].pluginsettings
+        self.settings = sys.modules["__main__"].settings
+        self.language = sys.modules["__main__"].language
+        self.plugin = sys.modules["__main__"].plugin
+        self.dbg = sys.modules["__main__"].dbg
+
+        self.utils = sys.modules["__main__"].utils
+        self.core = sys.modules["__main__"].core
+        self.common = sys.modules["__main__"].common
+
+    def login(self, params={}):
+        get = params.get
+        self.common.log("")
+
+        old_user_name = self.pluginsettings.userName()
+        old_user_password = self.pluginsettings.userPassword()
+        self.settings.openSettings()
+
+        user_name = self.pluginsettings.userName()
+        user_password = self.pluginsettings.userPassword()
+
+        self.dbg = self.pluginsettings.debugModeIsEnabled()
+        result = ""
+        status = 500
+
+        if not user_name:
+            return (result, 200)
+
+        refreshed = False
+        if get("new", "false") == "false" and self.pluginsettings.authenticationRefreshRoken and old_user_name == user_name and old_user_password == user_password:
+            self.common.log("refreshing token: " + str(refreshed))
+            refreshed = self.core._oRefreshToken()
+
+        if not refreshed:
+            result, status = self.authorize()
+
+        self.xbmc.executebuiltin("Container.Refresh")
+        return (result, status)
+
+    def authorize(self):
+        self.common.log("token not refresh, or new uname or password")
+        self.settings.setSetting("oauth2_access_token", "")
+        self.settings.setSetting("oauth2_refresh_token", "")
+        self.settings.setSetting("oauth2_expires_at", "")
+        (result, status) = self._httpLogin({"new": "true"})
+        if status == 200:
+            (result, status) = self._apiLogin()
+        if status == 200:
+            self.utils.showErrorMessage(self.language(30031), result, 303)
+        else:
+            self.utils.showErrorMessage(self.language(30609), result, status)
+        return result, status
+
+    def _apiLogin(self):
+        self.common.log("")
+
+        url = self.urls[u"oauth_api_login"]
+
+        logged_in = False
+        fetch_options = {"link": url, "no-language-cookie": "true"}
+        step = 0
+        self.common.log("Part A")
+        while not logged_in and fetch_options and step < 6:
+            self.common.log("Step : " + str(step))
+            step += 1
+
+            ret = self.core._fetchPage(fetch_options)
+            fetch_options = False
+
+            newurl = self.common.parseDOM(ret["content"], "form", attrs={"method": "POST"}, ret="action")
+            state_wrapper = self.common.parseDOM(ret["content"], "input", attrs={"id": "state_wrapper"}, ret="value")
+
+            if len(newurl) > 0 and len(state_wrapper) > 0:
+                url_data = {"state_wrapper": state_wrapper[0],
+                            "submit_access": "true"}
+
+                fetch_options = {"link": newurl[0].replace("&amp;", "&"), "url_data": url_data, "no-language-cookie": "true"}
+                self.common.log("Part B")
+                continue
+
+            code = self.common.parseDOM(ret["content"], "input", attrs={"id": "code"}, ret="value")
+            if len(code) > 0:
+                url = "https://accounts.google.com/o/oauth2/token"
+                url_data = {"client_id": "208795275779.apps.googleusercontent.com",
+                            "client_secret": "sZn1pllhAfyonULAWfoGKCfp",
+                            "code": code[0],
+                            "redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
+                            "grant_type": "authorization_code"}
+                fetch_options = {"link": url, "url_data": url_data}
+                self.common.log("Part C")
+                continue
+
+            # use token
+            if ret["content"].find("access_token") > -1:
+                self.common.log("Part D")
+                oauth = json.loads(ret["content"])
+
+                if len(oauth) > 0:
+                    self.common.log("Part D " + repr(oauth["expires_in"]))
+                    self.settings.setSetting("oauth2_expires_at", str(int(oauth["expires_in"]) + time.time()))
+                    self.settings.setSetting("oauth2_access_token", oauth["access_token"])
+                    self.settings.setSetting("oauth2_refresh_token", oauth["refresh_token"])
+
+                    logged_in = True
+                    self.common.log("Done:" + self.settings.getSetting("username"))
+
+        if logged_in:
+            return (self.language(30030), 200)
+        else:
+            self.common.log("Failed")
+            return (self.language(30609), 303)
+
+    def _httpLogin(self, params={}):
+        get = params.get
+        self.common.log("")
+        status = 500
+
+        if get("new", "false") == "true" or get("page", "false") != "false":
+            self.settings.setSetting("login_info", "")
+            self.settings.setSetting("SID", "")
+            self.settings.setSetting("login_cookies", "")
+        elif self.settings.getSetting("login_info") != "":
+            self.common.log("returning existing login info: " + self.settings.getSetting("login_info"))
+            return (self.settings.getSetting("login_info"), 200)
+
+        fetch_options = {"link": get("link", "http://www.youtube.com/")}
+
+        step = 0
+        galx = ""
+        ret = {}
+
+        while fetch_options and step < 18:  # 6 steps for 2-factor login
+            self.common.log("Step : " + str(step))
+            step += 1
+
+            if step == 17:
+                return (self.core._findErrors(ret), 303)
+
+            ret = self.core._fetchPage(fetch_options)
+
+            if ret["content"].find(" captcha") > -1:
+                self.common.log("Captcha needs to be filled")
+                break
+            fetch_options = False
+
+            # Check if we are logged in.
+            nick = self.common.parseDOM(ret["content"], "p", attrs={"class": "masthead-expanded-acct-sw-id2"})
+
+            # Check if there are any errors to report
+            errors = self.core._findErrors(ret, silent=True)
+            if errors:
+                if errors.find("cookie-clear-message-1") == -1 and (errors.find("The code you entered didn") == -1 or (errors.find("The code you entered didn") > -1 and step > 12)):
+                    self.common.log("Returning error: " + repr(errors))
+                    return (errors, 303)
+
+            if len(nick) > 0 and nick[0] != "Sign In":
+                self.common.log("Logged in. Parsing data: " + repr(nick))
+                status = self._getLoginInfo(nick)
+                return(ret, status)
+
+            # Click login link on youtube.com
+            newurl = self.common.parseDOM(ret["content"], "button", attrs={"href": ".*?ServiceLogin.*?"}, ret="href")
+            if len(newurl) > 0:
+                # Start login procedure
+                if newurl[0] != "#":
+                    fetch_options = {"link": newurl[0].replace("&amp;", "&"), "referer": ret["location"]}
+                    self.common.log("Part A : " + repr(fetch_options))
+
+            # Fill out login information and send.
+            newurl = self.common.parseDOM(ret["content"].replace("\n", " "), "form", attrs={"id": "gaia_loginform"}, ret="action")
+            if len(newurl) > 0:
+                (galx, url_data) = self._fillLoginInfo(ret["content"])
+                if len(galx) > 0 and len(url_data) > 0:
+                    fetch_options = {"link": newurl[0], "no-language-cookie": "true", "url_data": url_data, "hidden": "true", "referer": ret["location"]}
+                    self.common.log("Part B")
+                    self.common.log("fetch options: " + repr(fetch_options), 10)  # WARNING, SHOWS LOGIN INFO/PASSWORD
+                    continue
+
+            newurl = self.common.parseDOM(ret["content"], "meta", attrs={"http-equiv": "refresh"}, ret="content")
+            if len(newurl) > 0:
+                newurl = newurl[0].replace("&amp;", "&")
+                newurl = newurl[newurl.find("&#39;") + 5:newurl.rfind("&#39;")]
+                fetch_options = {"link": newurl, "no-language-cookie": "true", "referer": ret["location"]}
+                self.common.log("Part C: "  + repr(fetch_options))
+                continue
+
+            ## 2-factor login start
+            if ret["content"].find("smsUserPin") > -1:
+                url_data = self._fillUserPin(ret["content"])
+                if len(url_data) == 0:
+                    return (False, 500)
+
+                new_part = self.common.parseDOM(ret["content"], "form", attrs={"name": "verifyForm"}, ret="action")
+                fetch_options = {"link": new_part[0], "url_data": url_data, "no-language-cookie": "true", "referer": ret["location"]}
+
+                self.common.log("Part D: " + repr(fetch_options))
+                continue
+
+            smsToken = self.common.parseDOM(ret["content"].replace("\n", ""), "input", attrs={"name": "smsToken"}, ret="value")
+
+            if len(smsToken) > 0 and galx != "":
+                url_data = {"smsToken": smsToken[0],
+                            "PersistentCookie": "yes",
+                            "service": "youtube",
+                            "GALX": galx}
+
+                target_url = self.common.parseDOM(ret["content"], "form", attrs={"name": "hiddenpost"}, ret="action")
+                fetch_options = {"link": target_url[0], "url_data": url_data, "no-language-cookie": "true", "referer": ret["location"]}
+                self.common.log("Part E: " + repr(fetch_options))
+                continue
+
+            ## 2-factor login finish
+            if not fetch_options:
+                # Check for errors.
+                return (self.core._findErrors(ret), 303)
+
+        return (ret, status)
+
+    def _fillLoginInfo(self, content):
+        rmShown = self.common.parseDOM(content, "input", attrs={"name": "rmShown"}, ret="value")
+        cont = self.common.parseDOM(content, "input", attrs={"name": "continue"}, ret="value")
+        uilel = self.common.parseDOM(content, "input", attrs={"name": "uilel"}, ret="value")
+        if len(uilel) == 0:
+            uilel = self.common.parseDOM(content, "input", attrs= {"id":"uilel"}, ret="value")
+        dsh = self.common.parseDOM(content, "input", attrs={"name": "dsh"}, ret="value")
+        if len(dsh) == 0:
+            dsh = self.common.parseDOM(content, "input", attrs={"id": "dsh"}, ret="value")
+
+        galx = self.common.parseDOM(content, "input", attrs={"name": "GALX"}, ret="value")
+        uname = self.pluginsettings.userName()
+        pword = self.pluginsettings.userPassword()
+
+        if pword == "":
+            pword = self.common.getUserInput(self.language(30628), hidden=True)
+
+        if len(galx) == 0 or len(cont) == 0 or len(uilel) == 0 or len(dsh) == 0 or len(rmShown) == 0 or uname == "" or pword == "":
+            self.common.log("_fillLoginInfo missing values for login form " + repr(galx) + repr(cont) + repr(uilel) + repr(dsh) + repr(rmShown) + repr(uname) + str(len(pword)))
+            return ("", {})
+        else:
+            galx = galx[0]
+            url_data = {"pstMsg": "0",
+                        "ltmpl": "sso",
+                        "dnConn": "",
+                        "continue": cont[0],
+                        "service": "youtube",
+                        "uilel": uilel[0],
+                        "dsh": dsh[0],
+                        "hl": "en_US",
+                        "timeStmp": "",
+                        "secTok": "",
+                        "GALX": galx,
+                        "Email": uname,
+                        "Passwd": pword,
+                        "PersistentCookie": "yes",
+                        "rmShown": rmShown[0],
+                        "signin": "Sign in",
+                        "asts": ""
+                        }
+        return (galx, url_data)
+
+    def _fillUserPin(self, content):
+        self.common.log(repr(content), 5)
+        smsToken = self.common.parseDOM(content, "input", attrs={"name": "smsToken"}, ret="value")
+        self.smsToken = smsToken
+        userpin = self.common.getUserInputNumbers(self.language(30627))
+
+        if len(userpin) > 0:
+            url_data = {"smsToken": smsToken[0],
+                        "PersistentCookie": "yes",
+                        "smsUserPin": userpin,
+                        "smsVerifyPin": "Verify",
+                        "timeStmp": "",
+                        "secTok": ""}
+            self.common.log("Done: " + repr(url_data))
+            return url_data
+        else:
+            self.common.log("Replace this with a message telling users that they didn't enter a pin")
+            return {}
+
+    def _getLoginInfo(self, nick):
+        self.common.log(nick)
+        status = 303
+
+        # Save cookiefile in settings
+        cookies = self.common.getCookieInfoAsHTML()
+        login_info = self.common.parseDOM(cookies, "cookie", attrs={"name": "LOGIN_INFO"}, ret="value")
+        SID = self.common.parseDOM(cookies, "cookie", attrs={"name": "SID", "domain": ".youtube.com"}, ret="value")
+        scookies = {}
+        self.common.log("COOKIES:" + repr(cookies))
+        tnames = re.compile(" name='(.*?)' ").findall(cookies)
+        for key in tnames:
+            tval = self.common.parseDOM(cookies, "cookie", attrs={"name": key}, ret="value")
+            if len(tval) > 0:
+                scookies[key] = tval[0]
+        self.common.log("COOKIES:" + repr(scookies))
+
+        if len(login_info) == 1:
+            self.common.log("LOGIN_INFO: " + repr(login_info))
+            self.settings.setSetting("login_info", login_info[0])
+        else:
+            self.common.log("Failed to get LOGIN_INFO from youtube: " + repr(login_info))
+
+        if len(SID) == 1:
+            self.common.log("SID: " + repr(SID))
+            self.settings.setSetting("SID", SID[0])
+        else:
+            self.common.log("Failed to get SID from youtube: " + repr(SID))
+
+        if len(SID) == 1 and len(login_info) == 1:
+            status = 200
+            self.settings.setSetting("login_cookies", repr(scookies))
+
+        self.common.log("Done")
+        return status
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/YouTubeNavigation.py	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,712 @@
+'''
+   YouTube plugin for XBMC
+   Copyright (C) 2010-2012 Tobias Ussing And Henrik Mosgaard Jensen
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import urllib
+
+# Dorfelite
+try:
+  import json
+except:
+  import simplejson as json
+
+from threading import Thread
+
+# Dorfelite
+DORFHOST = "http://dorfelite.net:31337"
+
+class YouTubeNavigation():
+    def __init__(self):
+        self.xbmc = sys.modules["__main__"].xbmc
+        self.xbmcgui = sys.modules["__main__"].xbmcgui
+        self.xbmcplugin = sys.modules["__main__"].xbmcplugin
+
+        self.settings = sys.modules["__main__"].settings
+        self.language = sys.modules["__main__"].language
+        self.plugin = sys.modules["__main__"].plugin
+        self.dbg = sys.modules["__main__"].dbg
+
+        self.utils = sys.modules["__main__"].utils
+        self.core = sys.modules["__main__"].core
+        self.common = sys.modules["__main__"].common
+        self.cache = sys.modules["__main__"].cache
+
+        self.pluginsettings = sys.modules["__main__"].pluginsettings
+        self.playlist = sys.modules["__main__"].playlist
+        self.login = sys.modules["__main__"].login
+        self.feeds = sys.modules["__main__"].feeds
+        self.player = sys.modules["__main__"].player
+        self.downloader = sys.modules["__main__"].downloader
+        self.storage = sys.modules["__main__"].storage
+        self.scraper = sys.modules["__main__"].scraper
+        self.subtitles = sys.modules["__main__"].subtitles
+
+        # This list contains the main menu structure the user first encounters when running the plugin
+        #     label                        , path                                          , thumbnail                    ,  login                  ,  feed / action
+        self.categories = (
+            {'Title':self.language(30044)  ,'path':"/root/explore"                         , 'thumbnail':"explore"           , 'login':"false" },
+            {'Title':self.language(30041)  ,'path':"/root/explore/categories"              , 'thumbnail':"explore"           , 'login':"false" , 'feed':'feed_categories', 'folder':'true'},
+            {'Title':self.language(30001)  ,'path':"/root/explore/feeds"                   , 'thumbnail':"feeds"             , 'login':"false" },
+            {'Title':self.language(30009)  ,'path':"/root/explore/feeds/discussed"         , 'thumbnail':"most"              , 'login':"false" , 'feed':"feed_discussed" },
+            {'Title':self.language(30010)  ,'path':"/root/explore/feeds/linked"            , 'thumbnail':"most"              , 'login':"false" , 'feed':"feed_linked" },
+            {'Title':self.language(30011)  ,'path':"/root/explore/feeds/viewed"            , 'thumbnail':"most"              , 'login':"false" , 'feed':"feed_viewed" },
+            {'Title':self.language(30012)  ,'path':"/root/explore/feeds/recent"            , 'thumbnail':"most"              , 'login':"false" , 'feed':"feed_recent" },
+            {'Title':self.language(30013)  ,'path':"/root/explore/feeds/responded"         , 'thumbnail':"most"              , 'login':"false" , 'feed':"feed_responded" },
+            {'Title':self.language(30050)  ,'path':"/root/explore/feeds/shared"            , 'thumbnail':"most"              , 'login':"false" , 'feed':"feed_shared" },
+            {'Title':self.language(30014)  ,'path':"/root/explore/feeds/featured"          , 'thumbnail':"featured"          , 'login':"false" , 'feed':"feed_featured" },
+            {'Title':self.language(30049)  ,'path':"/root/explore/feeds/trending"          , 'thumbnail':"featured"          , 'login':"false" , 'feed':"feed_trending" },
+            {'Title':self.language(30015)  ,'path':"/root/explore/feeds/favorites"         , 'thumbnail':"top"               , 'login':"false" , 'feed':"feed_favorites" },
+            {'Title':self.language(30016)  ,'path':"/root/explore/feeds/rated"             , 'thumbnail':"top"               , 'login':"false" , 'feed':"feed_rated" },
+            {'Title':self.language(30052)  ,'path':"/root/explore/music"                   , 'thumbnail':"music"             , 'login':"false" , 'store':"disco_searches", "folder":"true" },
+            {'Title':self.language(30040)  ,'path':"/root/explore/music/new"               , 'thumbnail':"search"            , 'login':"false" , 'scraper':"search_disco"},
+            {'Title':self.language(30055)  ,'path':"/root/explore/music/top100"            , 'thumbnail':"music"             , 'login':"false" , 'scraper':'music_top100'},
+            {'Title':self.language(30032)  ,'path':"/root/explore/trailers"                , 'thumbnail':"trailers"          , 'login':"false" , 'scraper':'trailers'},
+            {'Title':self.language(30051)  ,'path':"/root/explore/live"                    , 'thumbnail':"live"              , 'login':"false" , 'feed':"feed_live" },
+            {'Title':self.language(30019)  ,'path':"/root/recommended"                     , 'thumbnail':"recommended"       , 'login':"true"  , 'user_feed':"recommended" },
+            {'Title':self.language(30008)  ,'path':"/root/watch_later"                     , 'thumbnail':"watch_later"       , 'login':"true"  , 'user_feed':"watch_later" },
+            {'Title':self.language(30056)  ,'path':"/root/liked"                           , 'thumbnail':"liked"             , 'login':"true"  , 'scraper':"liked_videos" },
+            {'Title':self.language(30059)  ,'path':"/root/history"                         , 'thumbnail':"history"           , 'login':"true"  , 'user_feed':"watch_history" },
+            {'Title':self.language(30018)  ,'path':"/root/contacts"                        , 'thumbnail':"contacts"          , 'login':"true"  , 'user_feed':"contacts", 'folder':'true' },
+            {'Title':self.language(30024)  ,'path':"/root/contacts/new"                    , 'thumbnail':"contacts"          , 'login':"true"  , 'action':"add_contact"},
+            {'Title':self.language(30002)  ,'path':"/root/favorites"                       , 'thumbnail':"favorites"         , 'login':"true"  , 'user_feed':"favorites" },
+            {'Title':self.language(30017)  ,'path':"/root/playlists"                       , 'thumbnail':"playlists"         , 'login':"true"  , 'user_feed':"playlists", 'folder':'true' },
+            {'Title':self.language(30003)  ,'path':"/root/subscriptions"                   , 'thumbnail':"subscriptions"     , 'login':"true"  , 'user_feed':"subscriptions", 'folder':'true' },
+            {'Title':self.language(30004)  ,'path':"/root/subscriptions/new"               , 'thumbnail':"newsubscriptions"  , 'login':"true"  , 'user_feed':"newsubscriptions" },
+            {'Title':self.language(30005)  ,'path':"/root/uploads"                         , 'thumbnail':"uploads"           , 'login':"true"  , 'user_feed':"uploads" },
+            {'Title':self.language(30045)  ,'path':"/root/downloads"                       , 'thumbnail':"downloads"         , 'login':"false" , 'feed':"downloads" },
+            {'Title':self.language(30006)  ,'path':"/root/search"                          , 'thumbnail':"search"            , 'login':"false" , 'store':"searches", 'folder':'true' },
+            {'Title':self.language(30007)  ,'path':"/root/search/new"                      , 'thumbnail':"search"            , 'login':"false" , 'feed':"search" },
+            {'Title':self.language(30027)  ,'path':"/root/login"                           , 'thumbnail':"login"             , 'login':"false" , 'action':"settings" },
+            {'Title':self.language(30028)  ,'path':"/root/settings"                        , 'thumbnail':"settings"          , 'login':"true"  , 'action':"settings" },
+
+            # Dorfelite
+            {'Title':self.language(31337)  ,'path':"/root/dorfelite"                       , 'thumbnail':"discoball"         , 'login':"false" , 'action':"dorfelite" }
+                                         )
+
+    #==================================== Main Entry Points===========================================
+    def listMenu(self, params={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+        cache = True
+
+        path = get("path", "/root")
+        if get("feed") not in ["search", "related"] and not get("channel") and not get("contact") and not get("playlist") and get("page", "0") == "0" and get("scraper") not in ["search_disco", "music_artist"]:
+            for category in self.categories:
+                cat_get = category.get
+                if (cat_get("path").find(path + "/") > - 1):
+                    if (cat_get("path").rfind("/") <= len(path + "/")):
+                        setting = self.settings.getSetting(cat_get("path").replace("/root/explore/", "").replace("/root/", ""))
+                        if not setting or setting == "true":
+                            if (cat_get("feed") == "downloads"):
+                                if (self.settings.getSetting("downloadPath")):
+                                    self.addListItem(params, category)
+                            else:
+                                self.addListItem(params, category)
+
+        if (get("feed") or get("user_feed") or get("options") or get("store") or get("scraper")):
+            return self.list(params)
+
+        video_view = self.settings.getSetting("list_view") == "1"
+
+        if (video_view):
+            self.xbmc.executebuiltin("Container.SetViewMode(500)")
+
+        self.xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=True, cacheToDisc=cache)
+        self.common.log("Done", 5)
+
+    def executeAction(self, params={}):
+        self.common.log(params, 3)
+        get = params.get
+        if (get("action") == "settings"):
+            self.login.login(params)
+        if (get("action") in ["delete_search", "delete_disco"]):
+            self.storage.deleteStoredSearch(params)
+        if (get("action") in ["edit_search", "edit_disco"]):
+            self.storage.editStoredSearch(params)
+            self.listMenu(params)
+        if (get("action") == "remove_favorite"):
+            self.removeFromFavorites(params)
+        if (get("action") == "add_favorite"):
+            self.addToFavorites(params)
+        if (get("action") == "remove_contact"):
+            self.removeContact(params)
+        if (get("action") == "add_contact"):
+            self.addContact(params)
+        if (get("action") == "remove_subscription"):
+            self.removeSubscription(params)
+        if (get("action") == "add_subscription"):
+            self.addSubscription(params)
+        if (get("action") == "download"):
+            self.downloadVideo(params)
+        if (get("action") == "play_video"):
+            self.player.playVideo(params)
+        if (get("action") == "queue_video"):
+            self.playlist.queueVideo(params)
+        if (get("action") == "change_subscription_view"):
+            self.storage.changeSubscriptionView(params)
+            self.list(params)
+        if (get("action") == "play_all"):
+            self.playlist.playAll(params)
+        if (get("action") == "add_to_playlist"):
+            self.playlist.addToPlaylist(params)
+        if (get("action") == "remove_from_playlist"):
+            self.playlist.removeFromPlaylist(params)
+        if (get("action") == "delete_playlist"):
+            self.playlist.deletePlaylist(params)
+        if (get("action") == "reverse_order"):
+            self.storage.reversePlaylistOrder(params)
+        if (get("action") == "create_playlist"):
+            self.playlist.createPlaylist(params)
+
+        # Dorfelite
+        if "dorfelite" == get("action"):
+          self.listDorfelite(params)
+
+        self.common.log("Done", 5)
+
+    #==================================== Item Building and Listing ===========================================
+    def list(self, params={}):
+        self.common.log("", 5)
+        get = params.get
+        results = []
+        if (get("feed") == "search" or get("scraper") == "search_disco"):
+            if not get("search"):
+                query = self.common.getUserInput(self.language(30006), '')
+                if not query:
+                    return False
+                params["search"] = query
+            if get("scraper") == "search_disco":
+                params["store"] = "disco_searches"
+            self.storage.saveStoredSearch(params)
+            if get("scraper") == "search_disco":
+                del params["store"]
+
+        if get("scraper"):
+            (results, status) = self.scraper.scrape(params)
+        elif get("store"):
+            (results, status) = self.storage.list(params)
+            self.common.log("store returned " + repr(results))
+        else:
+            (results, status) = self.feeds.list(params)
+
+        if status == 200:
+            if get("folder", "false") != "false":
+                self.parseFolderList(params, results)
+            else:
+                self.parseVideoList(params, results)
+                self.common.log("Done", 5)
+                return True
+        else:
+            self.showListingError(params)
+            self.common.log("Error")
+            return False
+
+    def showListingError(self, params={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+        label = ""
+        if get("external"):
+            categories = self.storage.user_options
+        else:
+            categories = self.categories
+
+        for category in categories:
+            cat_get = category.get
+            if (
+                (get("feed") and cat_get("feed") == get("feed")) or
+                (get("user_feed") and cat_get("user_feed") == get("user_feed")) or
+                (get("scraper") and cat_get("scraper") == get("scraper"))
+                ):
+                label = cat_get("Title")
+
+        if get("channel"):
+            label = get("channel")
+        if get("playlist"):
+            label = self.language(30615)
+        if label:
+            self.utils.showMessage(label, self.language(30601))
+        self.common.log("Done", 5)
+
+    #================================== Plugin Actions =========================================
+    def downloadVideo(self, params):
+        get = params.get
+        self.common.log(repr(params))
+        if not self.settings.getSetting("download_path"):
+            self.common.log("Download path missing. Opening settings")
+            self.utils.showMessage(self.language(30600), self.language(30611))
+            self.settings.openSettings()
+
+        download_path = self.settings.getSetting("download_path")
+        if not download_path:
+            return
+
+        self.common.log("path: " + repr(download_path))
+        (video, status) = self.player.buildVideoObject(params)
+
+        if "video_url" in video and download_path:
+            params["Title"] = video['Title']
+            params["url"] = video['video_url']
+            params["download_path"] = download_path
+            filename = "%s-[%s].mp4" % (''.join(c for c in video['Title'] if c not in self.utils.INVALID_CHARS), video["videoid"])
+
+            self.subtitles.downloadSubtitle(video)
+            if get("async"):
+                self.downloader.download(filename, params, async=False)
+            else:
+                self.downloader.download(filename, params)
+        else:
+            if "apierror" in video:
+                self.utils.showMessage(self.language(30625), video["apierror"])
+            else:
+                self.utils.showMessage(self.language(30625), "ERROR")
+
+    def addToFavorites(self, params={}):
+        self.common.log("", 5)
+        get = params.get
+        if (get("videoid")):
+            (message, status) = self.core.add_favorite(params)
+            if status != 200:
+                self.utils.showErrorMessage(self.language(30020), message, status)
+                self.common.log("Error", 5)
+                return False
+        self.common.log("Done", 5)
+        return True
+
+    def removeFromFavorites(self, params={}):
+        self.common.log("", 5)
+        get = params.get
+
+        if (get("editid")):
+            (message, status) = self.core.delete_favorite(params)
+            if status != 200:
+                self.utils.showErrorMessage(self.language(30020), message, status)
+                return False
+            self.xbmc.executebuiltin("Container.Refresh")
+
+        self.common.log("Done", 5)
+        return True
+
+    def addContact(self, params={}):
+        self.common.log("", 5)
+        get = params.get
+
+        if not get("contact"):
+            contact = self.common.getUserInput(self.language(30519), '')
+            params["contact"] = contact
+
+        if (get("contact")):
+            (result, status) = self.core.add_contact(params)
+            if status != 200:
+                self.utils.showErrorMessage(self.language(30029), result, status)
+                return False
+            self.utils.showMessage(self.language(30613), get("contact"))
+            self.xbmc.executebuiltin("Container.Refresh")
+
+        self.common.log("Done", 5)
+        return True
+
+    def removeContact(self, params={}):
+        self.common.log("", 5)
+        get = params.get
+
+        if (get("contact")):
+            (result, status) = self.core.remove_contact(params)
+            if status != 200:
+                self.utils.showErrorMessage(self.language(30029), result, status)
+                return False
+
+            self.utils.showMessage(self.language(30614), get("contact"))
+            self.xbmc.executebuiltin("Container.Refresh")
+        return True
+
+    def addSubscription(self, params={}):
+        self.common.log("", 5)
+        get = params.get
+        if (get("channel")):
+            (message, status) = self.core.add_subscription(params)
+            if status != 200:
+                self.utils.showErrorMessage(self.language(30021), message, status)
+                return False
+        self.common.log("Done", 5)
+        return True
+
+    def removeSubscription(self, params={}):
+        self.common.log("", 5)
+        get = params.get
+        if (get("editid")):
+            (message, status) = self.core.remove_subscription(params)
+            if status != 200:
+                self.utils.showErrorMessage(self.language(30021), message, status)
+                return False
+
+            self.xbmc.executebuiltin("Container.Refresh")
+        self.common.log("Done", 5)
+        return True
+
+    #================================== List Item manipulation =========================================
+    # is only used by List Menu
+    def addListItem(self, params={}, item_params={}):
+        self.common.log("", 5)
+        item = item_params.get
+
+        if (not item("action")):
+            if (item("login") == "false"):
+                self.addFolderListItem(params, item_params)
+            else:
+                if (len(self.settings.getSetting("oauth2_access_token")) > 0):
+                    self.addFolderListItem(params, item_params)
+        else:
+            if (item("action") == "settings"):
+                if (len(self.settings.getSetting("oauth2_access_token")) > 0):
+                    if (item("login") == "true"):
+                        self.addActionListItem(params, item_params)
+                else:
+                    if (item("login") == "false"):
+                        self.addActionListItem(params, item_params)
+            elif (item("action") == "play_video"):
+                self.addVideoListItem(params, item_params, 0)
+            else:
+                self.addActionListItem(params, item_params)
+        self.common.log("Done", 5)
+
+    # common function for adding folder items
+    def addFolderListItem(self, params={}, item_params={}, size=0):
+        self.common.log("", 5)
+        item = item_params.get
+
+        icon = "DefaultFolder.png"
+        if item("icon"):
+            icon = self.utils.getThumbnail(item("icon"))
+
+        thumbnail = item("thumbnail")
+
+        cm = self.addFolderContextMenuItems(params, item_params)
+
+        if (item("thumbnail", "DefaultFolder.png").find("http://") == - 1):
+            thumbnail = self.utils.getThumbnail(item("thumbnail"))
+
+        listitem = self.xbmcgui.ListItem(item("Title"), iconImage=icon, thumbnailImage=thumbnail)
+        url = '%s?path=%s&' % (sys.argv[0], item("path"))
+        url = self.utils.buildItemUrl(item_params, url)
+
+        if len(cm) > 0:
+            listitem.addContextMenuItems(cm, replaceItems=False)
+
+        listitem.setProperty("Folder", "true")
+        if (item("feed") == "downloads"):
+            url = self.settings.getSetting("downloadPath")
+        self.xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listitem, isFolder=True, totalItems=size)
+        self.common.log("Done", 5)
+
+    # common function for adding action items
+    def addActionListItem(self, params={}, item_params={}, size=0):
+        self.common.log("", 5)
+        item = item_params.get
+        folder = True
+        icon = "DefaultFolder.png"
+        thumbnail = self.utils.getThumbnail(item("thumbnail"))
+        listitem = self.xbmcgui.ListItem(item("Title"), iconImage=icon, thumbnailImage=thumbnail)
+
+        if (item("action") == "playbyid"):
+            folder = False
+            listitem.setProperty('IsPlayable', 'true')
+
+        url = '%s?path=%s&' % (sys.argv[0], item("path"))
+        url += 'action=' + item("action") + '&'
+
+        self.xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listitem, isFolder=folder, totalItems=size)
+        self.common.log("Done", 5)
+
+    # common function for adding video items
+    def addVideoListItem(self, params={}, item_params={}, listSize=0):
+        self.common.log("", 5)
+        get = params.get
+        item = item_params.get
+
+        icon = item("icon", "default")
+        if(get("scraper", "").find("music") > -1):
+            icon = "music"
+        elif(get("scraper", "").find("disco") > -1):
+            icon = "discoball"
+        elif(get("feed", "").find("live") > -1):
+            icon = "live"
+
+        icon = self.utils.getThumbnail(icon)
+
+        listitem = self.xbmcgui.ListItem(item("Title"), iconImage=icon, thumbnailImage=item("thumbnail"))
+
+        url = '%s?path=%s&action=play_video&videoid=%s' % (sys.argv[0], "/root/video", item("videoid"))
+
+        if get("user_feed") == "watch_later":
+            url += "&watch_later=true&playlist_entry_id=%s&" % item("playlist_entry_id")
+
+        cm = self.addVideoContextMenuItems(params, item_params)
+
+        listitem.addContextMenuItems(cm, replaceItems=True)
+
+        listitem.setProperty("Video", "true")
+        listitem.setProperty("IsPlayable", "true")
+        listitem.setInfo(type='Video', infoLabels=item_params)
+        self.xbmcplugin.addDirectoryItem(handle=int(sys.argv[1]), url=url, listitem=listitem, isFolder=False, totalItems=listSize + 1)
+        self.common.log("Done", 5)
+
+    #==================================== Core Output Parsing Functions ===========================================
+
+    # Parses a folder list consisting of a tuple of dictionaries
+    def parseFolderList(self, params, results):
+        self.common.log("", 5)
+        listSize = len(results)
+        get = params.get
+
+        cache = True
+        if get("store") or get("user_feed"):
+            cache = False
+
+        for result_params in results:
+            result_params["path"] = get("path")
+            self.addFolderListItem(params, result_params, listSize + 1)
+
+        self.xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=True, cacheToDisc=cache)
+        self.common.log("Done", 5)
+
+    # parses a video list consisting of a tuple of dictionaries
+    def parseVideoList(self, params, results):
+        self.common.log("", 5)
+        listSize = len(results)
+        get = params.get
+
+        for result_params in results:
+            result_params["path"] = get("path")
+            result = result_params.get
+
+            if result("videoid") == "false":
+                continue
+
+            if get("scraper") == "watch_later":
+                result_params["index"] = str(results.index(result_params) + 1)
+
+            if result("next") == "true":
+                self.addFolderListItem(params, result_params, listSize)
+            else:
+                self.addVideoListItem(params, result_params, listSize)
+
+        video_view = int(self.settings.getSetting("list_view")) <= 1
+        if (video_view):
+            self.xbmc.executebuiltin("Container.SetViewMode(500)")
+
+        self.xbmcplugin.addSortMethod(handle=int(sys.argv[1]), sortMethod=self.xbmcplugin.SORT_METHOD_UNSORTED)
+        self.xbmcplugin.addSortMethod(handle=int(sys.argv[1]), sortMethod=self.xbmcplugin.SORT_METHOD_LABEL)
+        self.xbmcplugin.addSortMethod(handle=int(sys.argv[1]), sortMethod=self.xbmcplugin.SORT_METHOD_VIDEO_RATING)
+        self.xbmcplugin.addSortMethod(handle=int(sys.argv[1]), sortMethod=self.xbmcplugin.SORT_METHOD_DATE)
+        self.xbmcplugin.addSortMethod(handle=int(sys.argv[1]), sortMethod=self.xbmcplugin.SORT_METHOD_PROGRAM_COUNT)
+        self.xbmcplugin.addSortMethod(handle=int(sys.argv[1]), sortMethod=self.xbmcplugin.SORT_METHOD_VIDEO_RUNTIME)
+        self.xbmcplugin.addSortMethod(handle=int(sys.argv[1]), sortMethod=self.xbmcplugin.SORT_METHOD_GENRE)
+
+        self.xbmcplugin.endOfDirectory(handle=int(sys.argv[1]), succeeded=True, cacheToDisc=True)
+        self.common.log("Done", 5)
+
+    def addVideoContextMenuItems(self, params={}, item_params={}):
+        self.common.log("", 5)
+        cm = []
+        get = params.get
+        item = item_params.get
+
+        title = self.common.makeAscii(item("Title"))
+        url_title = urllib.quote_plus(title)
+        studio = self.common.makeAscii(item("Studio", "Unknown Author"))
+
+        # FIXME: WTF?
+        try:
+          print(studio)
+        except:
+          studio = "Dorfelite"
+
+        url_studio = urllib.quote_plus(studio)
+
+        cm.append((self.language(30504), "XBMC.Action(Queue)",))
+
+        if (get("playlist")):
+            cm.append((self.language(30521), "XBMC.RunPlugin(%s?path=%s&action=play_all&playlist=%s&videoid=%s&)" % (sys.argv[0], item("path"), get("playlist"), item("videoid"))))
+
+        if (get("user_feed") == "newsubscriptions" or get("user_feed") == "favorites"):
+            contact = "default"
+            if get("contact"):
+                contact = get("contact")
+            cm.append((self.language(30521), "XBMC.RunPlugin(%s?path=%s&action=play_all&user_feed=%s&contact=%s&videoid=%s&)" % (sys.argv[0], item("path"), get("user_feed"), contact, item("videoid"))))
+
+        cm.append((self.language(30501), "XBMC.RunPlugin(%s?path=%s&action=download&videoid=%s)" % (sys.argv[0], item("path"), item("videoid"))))
+
+        if (self.settings.getSetting("username") != "" and self.settings.getSetting("oauth2_access_token")):
+            if (get("user_feed") == "favorites" and not get("contact")):
+                cm.append((self.language(30506), 'XBMC.RunPlugin(%s?path=%s&action=remove_favorite&editid=%s&)' % (sys.argv[0], item("path"), item("editid"))))
+            else:
+                cm.append((self.language(30503), 'XBMC.RunPlugin(%s?path=%s&action=add_favorite&videoid=%s&)' % (sys.argv[0], item("path"), item("videoid"))))
+
+            if (get("external") == "true" or (get("feed") not in ["subscriptions_favorites", "subscriptions_uploads", "subscriptions_playlists"] and (get("user_feed") != "uploads" and not get("external")))):
+                cm.append((self.language(30512) % studio, 'XBMC.RunPlugin(%s?path=%s&channel=%s&action=add_subscription)' % (sys.argv[0], item("path"), url_studio)))
+
+            if (get("playlist") and item("playlist_entry_id")):
+                cm.append((self.language(30530), "XBMC.RunPlugin(%s?path=%s&action=remove_from_playlist&playlist=%s&playlist_entry_id=%s&)" % (sys.argv[0], item("path"), get("playlist"), item("playlist_entry_id"))))
+            cm.append((self.language(30528), "XBMC.RunPlugin(%s?path=%s&action=add_to_playlist&videoid=%s&)" % (sys.argv[0], item("path"), item("videoid"))))
+
+        if (get("feed") != "uploads" and get("user_feed") != "uploads"):
+            cm.append((self.language(30516) % studio, "XBMC.Container.Update(%s?path=%s&feed=uploads&channel=%s)" % (sys.argv[0], get("path"), url_studio)))
+
+        cm.append((self.language(30514), "XBMC.Container.Update(%s?path=%s&feed=search&search=%s)" % (sys.argv[0], get("path"), url_title)))
+        cm.append((self.language(30527), "XBMC.Container.Update(%s?path=%s&feed=related&videoid=%s)" % (sys.argv[0], get("path"), item("videoid"))))
+        cm.append((self.language(30523), "XBMC.ActivateWindow(VideoPlaylist)"))
+        cm.append((self.language(30502), "XBMC.Action(Info)",))
+
+        self.common.log("Done", 5)
+        return cm
+
+    def addFolderContextMenuItems(self, params={}, item_params={}):
+        self.common.log("", 5)
+        cm = []
+        get = params.get
+        item = item_params.get
+
+        if (item("next", "false") == "true"):
+            return cm
+
+        if (item("user_feed") in ["favorites", "newsubscriptions", "watch_later", "recommended"]):
+            cm.append((self.language(30520), "XBMC.RunPlugin(%s?path=%s&action=play_all&user_feed=%s&contact=%s&login=true&)" % (sys.argv[0], item("path"), item("user_feed"), "default")))
+            cm.append((self.language(30522), "XBMC.RunPlugin(%s?path=%s&action=play_all&shuffle=true&user_feed=%s&contact=%s&login=true&)" % (sys.argv[0], item("path"), item("user_feed"), "default")))
+
+        if item("scraper") in ["liked_videos"]:
+            cm.append((self.language(30520), "XBMC.RunPlugin(%s?path=%s&action=play_all&scraper=%s&login=true&)" % (sys.argv[0], item("path"), item("scraper"))))
+            cm.append((self.language(30522), "XBMC.RunPlugin(%s?path=%s&action=play_all&shuffle=true&scraper=%s&login=true&)" % (sys.argv[0], item("path"), item("scraper"))))
+
+        if (item("playlist")):
+            cm.append((self.language(30531), "XBMC.RunPlugin(%s?path=%s&action=reverse_order&playlist=%s&)" % (sys.argv[0], item("path"), item("playlist"))))
+            cm.append((self.language(30520), "XBMC.RunPlugin(%s?path=%s&action=play_all&user_feed=playlist&playlist=%s&)" % (sys.argv[0], item("path"), item("playlist"))))
+            cm.append((self.language(30522), "XBMC.RunPlugin(%s?path=%s&action=play_all&user_feed=playlist&shuffle=true&playlist=%s&)" % (sys.argv[0], item("path"), item("playlist"))))
+            if not get("external"):
+                cm.append((self.language(30539), "XBMC.RunPlugin(%s?path=%s&action=delete_playlist&playlist=%s&)" % (sys.argv[0], item("path"), item("playlist"))))
+
+        if (item("scraper") == "music_top100"):
+            cm.append((self.language(30520), "XBMC.RunPlugin(%s?path=%s&action=play_all&scraper=music_top100&)" % (sys.argv[0], item("path"))))
+            cm.append((self.language(30522), "XBMC.RunPlugin(%s?path=%s&action=play_all&shuffle=true&scraper=music_top100&)" % (sys.argv[0], item("path"))))
+
+        if (item("scraper") == "search_disco"):
+            cm.append((self.language(30520), "XBMC.RunPlugin(%s?path=%s&action=play_all&scraper=search_disco&search=%s&)" % (sys.argv[0], item("path"), item("search"))))
+            cm.append((self.language(30522), "XBMC.RunPlugin(%s?path=%s&action=play_all&shuffle=true&scraper=search_disco&search=%s&)" % (sys.argv[0], item("path"), item("search"))))
+            if not get("scraper") == "disco_top_artist":
+                cm.append((self.language(30524), 'XBMC.Container.Update(%s?path=%s&action=edit_disco&store=disco_searches&search=%s&)' % (sys.argv[0], item("path"), item("search"))))
+                cm.append((self.language(30525), 'XBMC.RunPlugin(%s?path=%s&action=delete_disco&store=disco_searches&delete=%s&)' % (sys.argv[0], item("path"), item("search"))))
+
+        if (item("feed") == "search"):
+            cm.append((self.language(30515), 'XBMC.Container.Update(%s?path=%s&action=edit_search&store=searches&search=%s&)' % (sys.argv[0], item("path"), item("search"))))
+            cm.append((self.language(30508), 'XBMC.RunPlugin(%s?path=%s&action=delete_search&store=searches&delete=%s&)' % (sys.argv[0], item("path"), item("search"))))
+
+        if (item("view_mode")):
+            cm_url = 'XBMC.Container.Update(%s?path=%s&channel=%s&action=change_subscription_view&view_mode=%s&' % (sys.argv[0], item("path"), item("channel"), "%s")
+            if (item("external")):
+                cm_url += "external=true&contact=" + get("contact") + "&"
+            cm_url += ")"
+
+            if (item("user_feed") == "favorites"):
+                cm.append((self.language(30511), cm_url % ("uploads")))
+                cm.append((self.language(30526), cm_url % ("playlists&folder=true")))
+            elif(item("user_feed") == "playlists"):
+                cm.append((self.language(30511), cm_url % ("uploads")))
+                cm.append((self.language(30510), cm_url % ("favorites")))
+            elif (item("user_feed") == "uploads"):
+                cm.append((self.language(30510), cm_url % ("favorites")))
+                cm.append((self.language(30526), cm_url % ("playlists&folder=true")))
+
+        if (item("channel") or item("contact")):
+            if (self.settings.getSetting("username") != "" and self.settings.getSetting("oauth2_access_token")):
+                title = self.common.makeAscii(item("channel", ""))
+                if (get("external")):
+                    channel = get("channel", "")
+                    if not channel:
+                        channel = get("contact")
+                    cm.append((self.language(30512) % title, 'XBMC.RunPlugin(%s?path=%s&channel=%s&action=add_subscription)' % (sys.argv[0], item("path"), channel)))
+                elif item("editid"):
+                    cm.append((self.language(30513) % title, 'XBMC.RunPlugin(%s?path=%s&editid=%s&action=remove_subscription)' % (sys.argv[0], item("path"), item("editid"))))
+
+        if (item("contact") and not get("store")):
+            if (self.pluginsettings.userHasProvidedValidCredentials()):
+                if (item("external")):
+                    cm.append((self.language(30026), 'XBMC.RunPlugin(%s?path=%s&action=add_contact&contact=%s&)' % (sys.argv[0], item("path"), item("Title"))))
+                else:
+                    cm.append((self.language(30025), 'XBMC.RunPlugin(%s?path=%s&action=remove_contact&contact=%s&)' % (sys.argv[0], item("path"), item("Title"))))
+
+        cm.append((self.language(30523), "XBMC.ActivateWindow(VideoPlaylist)"))
+        self.common.log("Done", 5)
+        return cm
+
+  #==================================== Dorfelite ===========================================
+
+    ## saveDorfelite {{{
+    # Post new entry to server
+    # @param  self    Stupid..
+    # @param  params  Passed parameters
+    ##
+
+    def saveDorfelite(self, params):
+      # Get video info
+      (info, status) = player.getInfo(params)
+
+      values = urllib.urlencode({ "videoid": params["videoid"], "Title": info["Title"] })
+      con    = urllib.urlopen(DORFHOST, values)
+      result = con.read() #< Ignore reply
+      con.close()
+    # }}}
+
+    ## playDorfelite {{{
+    # Post new entry to server and play vid
+    # @param  self    Stupid..
+    # @param  params  Passed parameters
+    ##
+
+    def playDorfelite(self, params = {}):
+      t = Thread(target = self.saveDorfelite, args = (params,))
+      t.start()
+
+      player.playVideo(params)
+
+      return True
+    # }}}
+
+    ## listDorfelite {{{
+    # Fetch list from server and build list
+    # @param  self    Stupid..
+    # @param  params  Passed parameters
+    ##
+
+    def listDorfelite(self, params = {}):
+      con    = urllib.urlopen(DORFHOST)
+      result = con.read()
+      con.close()
+
+      # Hack?
+      params["path"] = "/root/dorfelite"
+
+      # Prepare list
+      videos = json.loads(result)
+
+      for idx, v in enumerate(videos):
+        videos[idx]["video_url"] = "http://www.youtube.com/watch?v=%s&safeSearch=none" % videos[idx]["videoid"]
+        videos[idx]["Studio"]    = "Dorfelite"
+
+      self.parseVideoList(params, videos)
+
+      return True
+    # }}}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/YouTubePlayer.py	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,423 @@
+'''
+   YouTube plugin for XBMC
+    Copyright (C) 2010-2012 Tobias Ussing And Henrik Mosgaard Jensen
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import urllib
+import cgi
+try: import simplejson as json
+except ImportError: import json
+
+from threading import Thread
+
+# Dorfelite
+DORFHOST = "http://dorfelite.net:31337"
+
+class YouTubePlayer():
+    fmt_value = {
+        5: "240p h263 flv container",
+        18: "360p h264 mp4 container | 270 for rtmpe?",
+        22: "720p h264 mp4 container",
+        26: "???",
+        33: "???",
+        34: "360p h264 flv container",
+        35: "480p h264 flv container",
+        37: "1080p h264 mp4 container",
+        38: "720p vp8 webm container",
+        43: "360p h264 flv container",
+        44: "480p vp8 webm container",
+        45: "720p vp8 webm container",
+        46: "520p vp8 webm stereo",
+        59: "480 for rtmpe",
+        78: "seems to be around 400 for rtmpe",
+        82: "360p h264 stereo",
+        83: "240p h264 stereo",
+        84: "720p h264 stereo",
+        85: "520p h264 stereo",
+        100: "360p vp8 webm stereo",
+        101: "480p vp8 webm stereo",
+        102: "720p vp8 webm stereo",
+        120: "hd720",
+        121: "hd1080"
+        }
+
+    # YouTube Playback Feeds
+    urls = {}
+    urls['video_stream'] = "http://www.youtube.com/watch?v=%s&safeSearch=none"
+    urls['embed_stream'] = "http://www.youtube.com/get_video_info?video_id=%s"
+    urls['video_info'] = "http://gdata.youtube.com/feeds/api/videos/%s"
+
+    def __init__(self):
+        self.xbmcgui = sys.modules["__main__"].xbmcgui
+        self.xbmcplugin = sys.modules["__main__"].xbmcplugin
+
+        self.pluginsettings = sys.modules["__main__"].pluginsettings
+        self.storage = sys.modules["__main__"].storage
+        self.settings = sys.modules["__main__"].settings
+        self.language = sys.modules["__main__"].language
+        self.dbg = sys.modules["__main__"].dbg
+
+        self.common = sys.modules["__main__"].common
+        self.utils = sys.modules["__main__"].utils
+        self.cache = sys.modules["__main__"].cache
+        self.core = sys.modules["__main__"].core
+        self.login = sys.modules["__main__"].login
+        self.subtitles = sys.modules["__main__"].subtitles
+
+    def playVideo(self, params={}):
+        self.common.log(repr(params), 3)
+        get = params.get
+
+        (video, status) = self.buildVideoObject(params)
+
+        if status != 200:
+            self.common.log(u"construct video url failed contents of video item " + repr(video))
+            self.utils.showErrorMessage(self.language(30603), video["apierror"], status)
+            return False
+
+        listitem = self.xbmcgui.ListItem(label=video['Title'], iconImage=video['thumbnail'], thumbnailImage=video['thumbnail'], path=video['video_url'])
+
+        listitem.setInfo(type='Video', infoLabels=video)
+
+        self.common.log(u"Playing video: " + repr(video['Title']) + " - " + repr(get('videoid')) + " - " + repr(video['video_url']))
+
+        self.xbmcplugin.setResolvedUrl(handle=int(sys.argv[1]), succeeded=True, listitem=listitem)
+
+        if self.settings.getSetting("lang_code") != "0" or self.settings.getSetting("annotations") == "true":
+            self.subtitles.addSubtitles(video)
+
+        if (get("watch_later") == "true" and get("playlist_entry_id")):
+            self.common.log(u"removing video from watch later playlist")
+            self.core.remove_from_watch_later(params)
+
+        self.storage.storeValue("vidstatus-" + video['videoid'], "7")
+
+        # Dorfelite
+        t = Thread(target = self.saveDorfelite, args = ({ "videoid": video["videoid"], "Title": video["Title"] },))
+        t.start()
+
+    def getInfo(self, params):
+        get = params.get
+        video = self.cache.get("videoidcache" + get("videoid"))
+        if len(video) > 0:
+            self.common.log(u"returning cache ")
+            return (eval(video), 200)
+
+        result = self.core._fetchPage({"link": self.urls["video_info"] % get("videoid"), "api": "true"})
+
+        if result["status"] == 200:
+            video = self.core.getVideoInfo(result["content"], params)
+
+            if len(video) == 0:
+                self.common.log(u"- Couldn't parse API output, YouTube doesn't seem to know this video id?")
+                video = {}
+                video["apierror"] = self.language(30608)
+                return (video, 303)
+        else:
+            self.common.log(u"- Got API Error from YouTube!")
+            video = {}
+            video["apierror"] = result["content"]
+
+            return (video, 303)
+
+        video = video[0]
+        self.cache.set("videoidcache" + get("videoid"), repr(video))
+        return (video, result["status"])
+
+    def selectVideoQuality(self, params, links):
+        get = params.get
+
+        print "links: " + repr(type(links).__name__)
+        link = links.get
+        video_url = ""
+
+        self.common.log(u"")
+
+        if get("action") == "download":
+            hd_quality = int(self.settings.getSetting("hd_videos_download"))
+            if (hd_quality == 0):
+                hd_quality = int(self.settings.getSetting("hd_videos"))
+
+        else:
+            if (not get("quality")):
+                hd_quality = int(self.settings.getSetting("hd_videos"))
+            else:
+                if (get("quality") == "1080p"):
+                    hd_quality = 3
+                elif (get("quality") == "720p"):
+                    hd_quality = 2
+                else:
+                    hd_quality = 1
+
+        # SD videos are default, but we go for the highest res
+        if (link(35)):
+            video_url = link(35)
+        elif (link(59)):
+            video_url = link(59)
+        elif link(44):
+            video_url = link(44)
+        elif (link(78)):
+            video_url = link(78)
+        elif (link(34)):
+            video_url = link(34)
+        elif (link(43)):
+            video_url = link(43)
+        elif (link(26)):
+            video_url = link(26)
+        elif (link(18)):
+            video_url = link(18)
+        elif (link(33)):
+            video_url = link(33)
+        elif (link(5)):
+            video_url = link(5)
+
+        if hd_quality > 1:  # <-- 720p
+            if (link(22)):
+                video_url = link(22)
+            elif (link(45)):
+                video_url = link(45)
+            elif link(120):
+                video_url = link(120)
+        if hd_quality > 2:
+            if (link(37)):
+                video_url = link(37)
+            elif link(121):
+                video_url = link(121)
+
+        if link(38) and False:
+            video_url = link(38)
+
+        for fmt_key in links.iterkeys():
+            if link(int(fmt_key)):
+                if self.dbg:
+                    text = repr(fmt_key) + " - "
+                    if fmt_key in self.fmt_value:
+                        text += self.fmt_value[fmt_key]
+                    else:
+                        text += "Unknown"
+
+                    if (link(int(fmt_key)) == video_url):
+                        text += "*"
+                    self.common.log(text)
+            else:
+                self.common.log(u"- Missing fmt_value: " + repr(fmt_key))
+
+        if hd_quality == 0 and not get("quality"):
+            return self.userSelectsVideoQuality(params, links)
+
+        if not len(video_url) > 0:
+            self.common.log(u"- construct_video_url failed, video_url not set")
+            return video_url
+
+        if get("action") != "download":
+            video_url += " | " + self.common.USERAGENT
+
+        self.common.log(u"Done")
+        return video_url
+
+    def userSelectsVideoQuality(self, params, links):
+        levels =    [([37,121], u"1080p"),
+                     ([22,45,120], u"720p"),
+                     ([35,44], u"480p"),
+                     ([18], u"380p"),
+                     ([34,43],u"360p"),
+                     ([5],u"240p"),
+                     ([17],u"144p")]
+
+        link = links.get
+        quality_list = []
+        choices = []
+
+        for qualities, name in levels:
+            for quality in qualities:
+                if link(quality):
+                    quality_list.append((quality, name))
+                    break
+
+        for (quality, name) in quality_list:
+            choices.append(name)
+
+        dialog = self.xbmcgui.Dialog()
+        selected = dialog.select(self.language(30518), choices)
+
+        if selected > -1:
+            (quality, name) = quality_list[selected]
+            return link(quality)
+
+        return u""
+
+    def checkForErrors(self, video):
+        status = 200
+
+        if video[u"video_url"] == u"":
+            status = 303
+            vget = video.get
+            if vget(u"live_play"):
+                video[u'apierror'] = self.language(30612)
+            elif vget(u"stream_map"):
+                video[u'apierror'] = self.language(30620)
+            else:
+                video[u'apierror'] = self.language(30618)
+
+        return (video, status)
+
+    def buildVideoObject(self, params):
+        self.common.log(repr(params))
+
+        (video, status) = self.getInfo(params)
+
+        if status != 200:
+            video[u'apierror'] = self.language(30618)
+            return (video, 303)
+
+        video_url = self.subtitles.getLocalFileSource(params, video)
+        if video_url:
+            video[u'video_url'] = video_url
+            return (video, 200)
+
+        (links, video) = self.extractVideoLinksFromYoutube(video, params)
+
+        video[u"video_url"] = self.selectVideoQuality(params, links)
+
+        (video, status) = self.checkForErrors(video)
+
+        self.common.log(u"Done")
+
+        return (video, status)
+
+    def extractFlashVars(self, data):
+        flashvars = {}
+        found = False
+
+        for line in data.split("\n"):
+            if line.strip().startswith("var swf = \""):
+                found = True
+                p1 = line.find("=")
+                p2 = line.rfind(";")
+                if p1 <= 0 or p2 <= 0:
+                    continue
+                data = line[p1 + 1:p2]
+                break
+
+        if found:
+            data = json.loads(data)
+            data = data[data.find("flashvars"):]
+            data = data[data.find("\""):]
+            data = data[:1 + data[1:].find("\"")]
+
+            for k, v in cgi.parse_qs(data).items():
+                flashvars[k] = v[0]
+        self.common.log(u"flashvars: " + repr(flashvars), 2)
+        return flashvars
+
+    def scrapeWebPageForVideoLinks(self, result, video):
+        self.common.log(u"")
+        links = {}
+
+        flashvars = self.extractFlashVars(result[u"content"])
+        if not flashvars.has_key(u"url_encoded_fmt_stream_map"):
+            return links
+
+        if flashvars.has_key(u"ttsurl"):
+            video[u"ttsurl"] = flashvars[u"ttsurl"]
+
+        for url_desc in flashvars[u"url_encoded_fmt_stream_map"].split(u","):
+            url_desc_map = cgi.parse_qs(url_desc)
+            self.common.log(u"url_map: " + repr(url_desc_map), 2)
+            if not (url_desc_map.has_key(u"url") or url_desc_map.has_key(u"stream")):
+                continue
+
+            key = int(url_desc_map[u"itag"][0])
+            url = u""
+            if url_desc_map.has_key(u"url"):
+                url = urllib.unquote(url_desc_map[u"url"][0])
+            elif url_desc_map.has_key(u"conn") and url_desc_map.has_key(u"stream"):
+                url = urllib.unquote(url_desc_map[u"conn"][0])
+                if url.rfind("/") < len(url) -1:
+                    url = url + "/"
+                url = url + urllib.unquote(url_desc_map[u"stream"][0])
+            elif url_desc_map.has_key(u"stream") and not url_desc_map.has_key(u"conn"):
+                url = urllib.unquote(url_desc_map[u"stream"][0])
+
+            if url_desc_map.has_key(u"sig"):
+                url = url + u"&signature=" + url_desc_map[u"sig"][0]
+
+            links[key] = url
+
+        return links
+
+    def getVideoPageFromYoutube(self, get):
+        login = "false"
+
+        if self.pluginsettings.userHasProvidedValidCredentials():
+            login = "true"
+
+        page = self.core._fetchPage({u"link": self.urls[u"video_stream"] % get(u"videoid"), "login": login})
+
+        if not page:
+            page = {u"status":303}
+
+        return page
+
+    def isVideoAgeRestricted(self, result):
+        error = self.common.parseDOM(result['content'], "div", attrs={"id": "watch7-player-age-gate-content"})
+        self.common.log(repr(error))
+        return len(error) > 0
+
+    def extractVideoLinksFromYoutube(self, video, params):
+        self.common.log(u"trying website: " + repr(params))
+        get = params.get
+
+        result = self.getVideoPageFromYoutube(get)
+        if self.isVideoAgeRestricted(result) and self.pluginsettings.userName() != "":
+            self.login.login()
+            result = self.getVideoPageFromYoutube(get)
+
+        if self.isVideoAgeRestricted(result):
+            self.common.log(u"Age restricted video")
+            if not self.pluginsettings.userHasProvidedValidCredentials():
+                self.utils.showMessage(self.language(30600), self.language(30622))
+
+        if result[u"status"] != 200:
+            self.common.log(u"Couldn't get video page from YouTube")
+            return ({}, video)
+
+        links = self.scrapeWebPageForVideoLinks(result, video)
+
+        if len(links) == 0:
+            self.common.log(u"Couldn't find video url- or stream-map.")
+
+            if not u"apierror" in video:
+                video[u'apierror'] = self.core._findErrors(result)
+
+        self.common.log(u"Done")
+        return (links, video)
+
+  #==================================== Dorfelite ===========================================
+
+    ## saveDorfelite {{{
+    # Post new entry to server
+    # @param  self    Stupid..
+    # @param  params  Passed parameters
+    ##
+
+    def saveDorfelite(self, params):
+      values = urllib.urlencode({ "videoid": params["videoid"], "Title": params["Title"] })
+      con    = urllib.urlopen(DORFHOST, values)
+      result = con.read() #< Ignore reply
+      con.close()
+    # }}}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/YouTubePlaylistControl.py	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,233 @@
+'''
+   YouTube plugin for XBMC
+   Copyright (C) 2010-2012 Tobias Ussing And Henrik Mosgaard Jensen
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+
+
+class YouTubePlaylistControl():
+
+    def __init__(self):
+        self.xbmc = sys.modules["__main__"].xbmc
+        self.xbmcgui = sys.modules["__main__"].xbmcgui
+
+        self.settings = sys.modules["__main__"].settings
+        self.language = sys.modules["__main__"].language
+        self.plugin = sys.modules["__main__"].plugin
+        self.dbg = sys.modules["__main__"].dbg
+
+        self.common = sys.modules["__main__"].common
+        self.utils = sys.modules["__main__"].utils
+        self.core = sys.modules["__main__"].core
+
+        self.feeds = sys.modules["__main__"].feeds
+        self.scraper = sys.modules["__main__"].scraper
+        self.player = sys.modules["__main__"].player
+
+    def playAll(self, params={}):
+        get = params.get
+        self.common.log("")
+        params["fetch_all"] = "true"
+        result = []
+
+        # fetch the video entries
+        if get("scraper") == "search_disco":
+            (result, status) = self.scraper.searchDisco(params)
+            print repr(result)
+        elif get("scraper") == "liked_videos":
+            (result, status) = self.getLikedVideos(params)
+        elif get("scraper") == "music_top100":
+            result = self.getYouTubeTop100(params)
+        elif get("playlist"):
+            params["user_feed"] = "playlist"
+            result = self.getUserFeed(params)
+        elif get("user_feed") in ["recommended", "watch_later", "newsubscriptions", "favorites"]:
+            params["login"] = "true"
+            result = self.getUserFeed(params)
+        elif get("video_list"):
+            (ytobjects, status) = self.core.getBatchDetails(get("video_list").split(","))
+            result = ytobjects
+
+        if len(result) == 0:
+            self.common.log("no results")
+            return
+
+        self.common.log(repr(len(result)) + " video results ")
+
+        if get("videoid"):
+            video_index = -1
+            for index, video in enumerate(result):
+                vget = video.get
+                if vget("videoid") == get("videoid"):
+                    video_index = index
+            if video_index > -1:
+                result = result[video_index:]
+
+        player = self.xbmc.Player()
+        if (player.isPlaying()):
+            player.stop()
+
+        playlist = self.xbmc.PlayList(self.xbmc.PLAYLIST_VIDEO)
+        playlist.clear()
+
+        video_url = "%s?path=/root&action=play_video&videoid=%s"
+        # queue all entries
+        for entry in result:
+            video = entry.get
+            if video("videoid") == "false":
+                continue
+            listitem = self.xbmcgui.ListItem(label=video("Title"), iconImage=video("thumbnail"), thumbnailImage=video("thumbnail"))
+            listitem.setProperty('IsPlayable', 'true')
+            listitem.setProperty("Video", "true" )
+            listitem.setInfo(type='Video', infoLabels=entry)
+
+            playlist.add(video_url % (sys.argv[0], video("videoid") ), listitem)
+
+        if (get("shuffle")):
+            playlist.shuffle()
+
+        self.xbmc.executebuiltin('playlist.playoffset(video , 0)')
+
+    def queueVideo(self, params={}):
+        get = params.get
+        self.common.log("Queuing videos: " + get("videoid"))
+
+        items = []
+        videoids = get("videoid")
+
+        if videoids.find(','):
+            items = videoids.split(',')
+        else:
+            items.append(videoids)
+
+        (videos, status) = self.core.getBatchDetails(items, params)
+
+        if status != 200:
+            self.common.log("construct video url failed contents of video item " + repr(videos))
+
+            self.utils.showErrorMessage(self.language(30603), "apierror", status)
+            return False
+
+        playlist = self.xbmc.PlayList(self.xbmc.PLAYLIST_VIDEO)
+
+        video_url = "%s?path=/root&action=play_video&videoid=%s"
+        # queue all entries
+        for entry in videos:
+            video = entry.get
+            if video("videoid") == "false":
+                continue
+            listitem = self.xbmcgui.ListItem(label=video("Title"), iconImage=video("thumbnail"), thumbnailImage=video("thumbnail"))
+            listitem.setProperty('IsPlayable', 'true')
+            listitem.setProperty("Video", "true" )
+            listitem.setInfo(type='Video', infoLabels=entry)
+
+            playlist.add(video_url % (sys.argv[0], video("videoid") ), listitem)
+
+    def getUserFeed(self, params={}):
+        get = params.get
+
+        if get("user_feed") == "playlist":
+            if not get("playlist"):
+                return False
+        elif get("user_feed") in ["newsubscriptions", "favorites"]:
+            if not get("contact"):
+                return False
+
+        return self.feeds.listAll(params)
+
+    def getYouTubeTop100(self, params={}):
+        (result, status) = self.scraper.scrapeYouTubeTop100(params)
+
+        if status == 200:
+            (result, status) = self.core.getBatchDetails(result, params)
+
+        return result
+
+    def getLikedVideos(self, params={}):
+        get = params.get
+        if not get("scraper") or not get("login"):
+            return False
+
+        return self.scraper.scrapeUserLikedVideos(params)
+
+    def addToPlaylist(self, params={}):
+        get = params.get
+
+        result = []
+        if (not get("playlist")):
+            params["user_feed"] = "playlists"
+            params["login"] = "true"
+            params["folder"] = "true"
+            result = self.feeds.listAll(params)
+
+        selected = -1
+        if result:
+            list = []
+            list.append(self.language(30529))
+            for item in result:
+                list.append(item["Title"])
+            dialog = self.xbmcgui.Dialog()
+            selected = dialog.select(self.language(30528), list)
+
+        if selected == 0:
+            self.createPlaylist(params)
+            if get("title"):
+                result = self.feeds.listAll(params)
+                for item in result:
+                    if get("title") == item["Title"]:
+                        params["playlist"] = item["playlist"]
+                        break
+        elif selected > 0:
+            params["playlist"] = result[selected - 1].get("playlist")
+
+        if get("playlist"):
+            self.core.add_to_playlist(params)
+            return True
+
+        return False
+
+    def createPlaylist(self, params={}):
+        input = self.common.getUserInput(self.language(30529))
+        if input:
+            params["title"] = input
+            self.core.add_playlist(params)
+            return True
+        return False
+
+    def removeFromPlaylist(self, params={}):
+        get = params.get
+
+        if get("playlist") and get("playlist_entry_id"):
+            (message, status) = self.core.remove_from_playlist(params)
+
+            if (status != 200):
+                self.utils.showErrorMessage(self.language(30600), message, status)
+                return False
+
+            self.xbmc.executebuiltin("Container.Refresh")
+        return True
+
+    def deletePlaylist(self, params):
+        get = params.get
+        if get("playlist"):
+            (message, status) = self.core.del_playlist(params)
+            print "called " + repr(status)
+            if status != 200:
+                self.utils.showErrorMessage(self.language(30600), message, status)
+                return False
+            self.xbmc.executebuiltin("Container.Refresh")
+        return True
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/YouTubePluginSettings.py	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,51 @@
+'''
+   YouTube plugin for XBMC
+    Copyright (C) 2010-2012 Tobias Ussing And Henrik Mosgaard Jensen
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+import sys
+
+class YouTubePluginSettings():
+
+    def __init__(self):
+        self.settings = sys.modules["__main__"].settings
+        self.dbg = sys.modules["__main__"].dbg
+
+    def itemsPerPage(self):
+        return (10, 15, 20, 25, 30, 40, 50)[int(self.settings.getSetting("perpage"))]
+
+    def currentRegion(self):
+        return ('', 'AU', 'BR', 'CA', 'CZ', 'FR', 'DE', 'GB', 'NL', 'HK', 'IN', 'IE', 'IL', 'IT', 'JP', 'MX', 'NZ', 'PL', 'RU', 'KR', 'ES', 'SE', 'TW', 'US', 'ZA')[int(self.settings.getSetting("region_id"))]
+
+    def safeSearchLevel(self):
+        return ("none", "moderate", "strict")[int(self.settings.getSetting("safe_search"))]
+
+    def requestTimeout(self):
+        return [5, 10, 15, 20, 25][int(self.settings.getSetting("timeout"))]
+
+    def userHasProvidedValidCredentials(self):
+        return (self.settings.getSetting("username") != "" and self.settings.getSetting("oauth2_access_token"))
+
+    def userName(self):
+        return self.settings.getSetting("username")
+
+    def userPassword(self):
+        return self.settings.getSetting("user_password")
+
+    def debugModeIsEnabled(self):
+        return self.settings.getSetting("debug") == "true"
+
+    def authenticationRefreshRoken(self):
+        return self.settings.getSetting("oauth2_refresh_token")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/YouTubeScraper.py	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,249 @@
+'''
+   YouTube plugin for XBMC
+   Copyright (C) 2010-2012 Tobias Ussing And Henrik Mosgaard Jensen
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import urllib
+
+
+class YouTubeScraper():
+    urls = {}
+    urls['disco_main'] = "http://www.youtube.com/disco"
+    urls['disco_search'] = "http://www.youtube.com/disco?action_search=1&query=%s"
+    urls['main'] = "http://www.youtube.com"
+    urls['trailers'] = "http://www.youtube.com/trailers"
+    urls['liked_videos'] = "http://www.youtube.com/my_liked_videos"
+    urls['music'] = "http://www.youtube.com/music"
+    urls['playlist'] = "http://www.youtube.com/view_play_list?p=%s"
+
+    def __init__(self):
+        self.settings = sys.modules["__main__"].settings
+        self.language = sys.modules["__main__"].language
+        self.plugin = sys.modules["__main__"].plugin
+        self.dbg = sys.modules["__main__"].dbg
+
+        self.utils = sys.modules["__main__"].utils
+        self.core = sys.modules["__main__"].core
+        self.common = sys.modules["__main__"].common
+        self.cache = sys.modules["__main__"].cache
+
+        self.feeds = sys.modules["__main__"].feeds
+        self.storage = sys.modules["__main__"].storage
+
+#=================================== User Scraper ============================================
+
+    def scrapeUserLikedVideos(self, params):
+        self.common.log("")
+
+        url = self.createUrl(params)
+
+        result = self.core._fetchPage({"link": url, "login": "true"})
+
+        liked_playlist = self.common.parseDOM(result["content"], "button", {"id": "vm-playlist-play-all"}, ret="href")[0]
+
+        if (liked_playlist.rfind("list=") > 0):
+            liked_playlist = liked_playlist[liked_playlist.rfind("list=") + len("list="):]
+            if liked_playlist.rfind("&") > 0:
+                liked_playlist = liked_playlist[:liked_playlist.rfind("&")]
+
+            return self.feeds.listPlaylist({"user_feed": "playlist", "playlist" : liked_playlist, "fetch_all":"true", "login":"true"})
+
+        return ([], 303)
+
+#================================= trailers ===========================================
+
+    def scraperTop100Trailers(self, params):
+        self.common.log("" + repr(params))
+        url = self.createUrl(params)
+
+        result = self.core._fetchPage({"link":url})
+
+        trailers_playlist = self.common.parseDOM(result["content"], "a", attrs={"class":"yt-playall-link .*?"}, ret="href")[0]
+
+        if trailers_playlist.find("list=") > 0:
+            trailers_playlist = trailers_playlist[trailers_playlist.find("list=") + len("list="):]
+            if (trailers_playlist.rfind("&") > 0):
+                trailers_playlist = trailers_playlist[:trailers_playlist.rfind("&")]
+
+            return self.feeds.listPlaylist({"user_feed": "playlist", "playlist" : trailers_playlist})
+
+        return ([], 303)
+
+#=================================== Music ============================================
+
+    def searchDisco(self, params={}):
+        self.common.log("")
+
+        url = self.createUrl(params)
+        result = self.core._fetchPage({"link": url})
+
+        if (result["content"].find("list=") != -1):
+            result["content"] = result["content"].replace("\u0026", "&")
+            mix_list_id = result["content"][result["content"].find("list=") + 5:]
+            if (mix_list_id.find("&") != -1):
+                mix_list_id = mix_list_id[:mix_list_id.find("&")]
+            elif (mix_list_id.find('"') != -1):
+                mix_list_id = mix_list_id[:mix_list_id.find('"')]
+
+            return self.feeds.listPlaylist({"playlist": mix_list_id, "user_feed": "playlist", "fetch_all":"true"})
+
+        return ([], 303)
+
+    def scrapeYouTubeTop100(self, params={}):
+        self.common.log("")
+
+        url = self.createUrl(params)
+
+        result = self.core._fetchPage({"link": url})
+
+        if result["status"] == 200:
+            list_url = self.common.parseDOM(result["content"], "a", attrs={"id": 'popular-tracks'}, ret="href")[0]
+            return self.scrapeWeeklyTop100Playlist(list_url)
+
+        self.common.log("Done")
+        return ([], 303)
+
+    def scrapeWeeklyTop100Playlist(self, list_url):
+        self.common.log("")
+        url = self.urls["main"] + list_url
+
+        result = self.core._fetchPage({"link":url })
+
+        if result["status"] == 200:
+            playlist = self.common.parseDOM(result["content"], "ol", attrs={"id": 'watch7-playlist-tray'})
+            print repr(playlist)
+            videos = self.common.parseDOM(playlist, "li", attrs={"class": 'video-list-item.*?'}, ret="data-video-id")
+
+            return(videos, result["status"])
+
+        return ([], 303)
+        #================================== Common ============================================
+    def getNewResultsFunction(self, params={}):
+        get = params.get
+
+        function = ""
+        if (get("scraper") == "search_disco"):
+            function = self.searchDisco
+
+        if (get("scraper") in ["liked_videos", "watched_history"]):
+            function = self.scrapeUserLikedVideos
+
+        if (get("scraper") == "music_top100"):
+            params["batch"] = "true"
+            function = self.scrapeYouTubeTop100
+
+        if get("scraper") == "trailers":
+            function = self.scraperTop100Trailers
+
+        if function:
+            params["new_results_function"] = function
+
+        return True
+
+    def createUrl(self, params={}):
+        get = params.get
+        page = str(int(get("page", "0")) + 1)
+        url = ""
+
+        if (get("scraper") in self.urls):
+            url = self.urls[get("scraper")]
+            if url.find('%s') > 0:
+                url = url % page
+            elif url.find('?') > -1:
+                url += "&p=" + page
+            else:
+                url += "?p=" + page
+
+        if get("scraper") == "music_top100":
+            url = self.urls["disco_main"]
+
+        if get("scraper") == "trailers":
+            url = self.urls["trailers"]
+
+        if (get("scraper") in "search_disco"):
+            url = self.urls["disco_search"] % urllib.quote_plus(get("search"))
+
+        return url
+
+    def paginator(self, params={}):
+        self.common.log(repr(params))
+        get = params.get
+
+        status = 303
+        result = []
+        next = 'false'
+        page = int(get("page", "0"))
+        per_page = (10, 15, 20, 25, 30, 40, 50,)[int(self.settings.getSetting("perpage"))]
+
+        if get("page"):
+            del params["page"]
+
+        if (get("scraper") == "shows" and get("show")):
+            (result, status) = params["new_results_function"](params)
+        else:
+            (result, status) = self.cache.cacheFunction(params["new_results_function"], params)
+
+        self.common.log("paginator new result count " + str(repr(len(result[0:50]))))
+
+        if len(result) == 0:
+            if get("scraper") not in ["music_top100"]:
+                return (result, 303)
+            result = self.storage.retrieve(params)
+            if len(result) > 0:
+                status = 200
+        elif get("scraper") in ["music_top100"]:
+            self.storage.store(params, result)
+
+        if not get("folder"):
+            if (per_page * (page + 1) < len(result)):
+                next = 'true'
+
+            if (get("fetch_all") != "true"):
+                result = result[(per_page * page):(per_page * (page + 1))]
+
+            if len(result) == 0:
+                return (result, status)
+
+        if get("batch") == "thumbnails":
+            (result, status) = self.core.getBatchDetailsThumbnails(result, params)
+        elif get("batch"):
+            (result, status) = self.core.getBatchDetails(result, params)
+
+        if get("batch"):
+            del params["batch"]
+        if page > 0:
+            params["page"] = str(page)
+
+        if not get("page") and (get("scraper") == "search_disco"):
+            thumbnail = result[0].get("thumbnail")
+            self.storage.store(params, thumbnail, "thumbnail")
+
+        if next == "true":
+            self.utils.addNextFolder(result, params)
+
+        return (result, status)
+
+    def scrape(self, params={}):
+        get = params.get
+        if get("scraper") == "trailers":
+            return self.scraperTop100Trailers(params)
+
+        self.getNewResultsFunction(params)
+
+        result = self.paginator(params)
+        self.common.log(repr(result), 5)
+        return result
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/YouTubeStorage.py	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,522 @@
+'''
+   YouTube plugin for XBMC
+   Copyright (C) 2010-2012 Tobias Ussing And Henrik Mosgaard Jensen
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import urllib
+import io
+
+
+class YouTubeStorage():
+    def __init__(self):
+        self.xbmc = sys.modules["__main__"].xbmc
+        self.settings = sys.modules["__main__"].settings
+        self.language = sys.modules["__main__"].language
+        self.plugin = sys.modules["__main__"].plugin
+        self.dbg = sys.modules["__main__"].dbg
+
+        self.utils = sys.modules["__main__"].utils
+        self.common = sys.modules["__main__"].common
+        self.cache = sys.modules["__main__"].cache
+
+        # This list contains the list options a user sees when indexing a contact
+        #                label                      , external         , login         ,    thumbnail                    , feed
+        self.user_options = (
+                    {'Title':self.language(30020), 'external':"true", 'login':"true", 'thumbnail':"favorites",     'user_feed':"favorites"},
+                    {'Title':self.language(30023), 'external':"true", 'login':"true", 'thumbnail':"playlists",     'user_feed':"playlists", 'folder':"true"},
+                    {'Title':self.language(30021), 'external':"true", 'login':"true", 'thumbnail':"subscriptions", 'user_feed':"subscriptions", 'folder':"true"},
+                    {'Title':self.language(30022), 'external':"true", 'login':"true", 'thumbnail':"uploads",         'user_feed':"uploads"},
+                       )
+
+    def list(self, params={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+        if get("store") == "contact_options":
+            return self.getUserOptionFolder(params)
+        elif get("store") == "artists":
+            return self.getStoredArtists(params)
+        elif get("store"):
+            return self.getStoredSearches(params)
+        self.common.log("Done", 5)
+
+    def openFile(self, filepath, options="w"):
+        self.common.log(repr(filepath), 5)
+        if options.find("b") == -1:  # Toggle binary mode on failure
+            alternate = options + "b"
+        else:
+            alternate = options.replace("b", "")
+
+        try:
+            return io.open(filepath, options)
+        except:
+            return io.open(filepath, alternate)
+
+    def getStoredArtists(self, params={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+
+        artists = self.retrieve(params)
+
+        result = []
+        for title, artist in artists:
+            item = {}
+            item["path"] = get("path")
+            item["Title"] = urllib.unquote_plus(title)
+            item["artist"] = artist
+            item["scraper"] = "music_artist"
+            item["icon"] = "music"
+            item["thumbnail"] = "music"
+            thumbnail = self.retrieve(params, "thumbnail", item)
+
+            if thumbnail:
+                item["thumbnail"] = thumbnail
+
+            result.append(item)
+
+        return (result, 200)
+
+    def getStoredSearches(self, params={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+
+        searches = self.retrieveSettings(params)
+
+        result = []
+        for search in searches:
+            item = {}
+            item["path"] = get("path")
+            item["Title"] = search
+            item["search"] = urllib.quote_plus(search)
+
+            if (get("store") == "searches"):
+                item["feed"] = "search"
+                item["icon"] = "search"
+            elif get("store") == "disco_searches":
+                item["scraper"] = "search_disco"
+                item["icon"] = "discoball"
+
+            thumbnail = self.retrieveSettings(params, "thumbnail", item)
+            if thumbnail:
+                item["thumbnail"] = thumbnail
+            else:
+                item["thumbnail"] = item["icon"]
+            result.append(item)
+
+        self.common.log("Done: " + repr(result), 5)
+        return (result, 200)
+
+    def deleteStoredSearch(self, params={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+
+        query = urllib.unquote_plus(get("delete"))
+        searches = self.retrieveSettings(params)
+
+        for count, search in enumerate(searches):
+            if (search.lower() == query.lower()):
+                del(searches[count])
+                break
+
+        self.storeSettings(params, searches)
+
+        self.xbmc.executebuiltin("Container.Refresh")
+
+    def saveStoredSearch(self, params={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+
+        if get("search"):
+            searches = self.retrieveSettings(params)
+            self.common.log("1: " + repr(searches), 5)
+
+            new_query = urllib.unquote_plus(get("search"))
+            old_query = new_query
+
+            if get("old_search"):
+                old_query = urllib.unquote_plus(get("old_search"))
+
+            for count, search in enumerate(searches):
+                if (search.lower() == old_query.lower()):
+                    del(searches[count])
+                    break
+
+            searchCount = (10, 20, 30, 40,)[int(self.settings.getSetting("saved_searches"))] - 1
+            searches = [new_query] + searches[:searchCount]
+            self.common.log("2: " + repr(searches), 5)
+            self.storeSettings(params, searches)
+        self.common.log("Done", 5)
+
+    def editStoredSearch(self, params={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+
+        if (get("search")):
+            old_query = urllib.unquote_plus(get("search"))
+            new_query = self.common.getUserInput(self.language(30515), old_query)
+            params["search"] = new_query
+            params["old_search"] = old_query
+
+            if get("action") == "edit_disco":
+                params["scraper"] = "search_disco"
+                params["store"] = "disco_searches"
+            else:
+                params["store"] = "searches"
+                params["feed"] = "search"
+
+            self.saveStoredSearch(params)
+
+            params["search"] = urllib.quote_plus(new_query)
+            del params["old_search"]
+
+        if get("action"):
+            del params["action"]
+        if get("store"):
+            del params["store"]
+
+    def getUserOptionFolder(self, params={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+
+        result = []
+        for item in self.user_options:
+            item["path"] = get("path")
+            item["contact"] = get("contact")
+            result.append(item)
+
+        return (result, 200)
+
+    def changeSubscriptionView(self, params={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+
+        if (get("view_mode")):
+            key = self.getStorageKey(params, "viewmode")
+
+            self.storeValue(key, get("view_mode"))
+
+            params['user_feed'] = get("view_mode")
+            if get("viewmode") == "playlists":
+                params["folder"] = "true"  # No result
+
+    def reversePlaylistOrder(self, params={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+
+        if (get("playlist")):
+            value = "true"
+            existing = self.retrieve(params, "value")
+            if existing == "true":
+                value = "false"  # No result
+
+            self.store(params, value, "value")
+
+            self.xbmc.executebuiltin("Container.Refresh")
+
+    def getReversePlaylistOrder(self, params={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+
+        result = False
+        if (get("playlist")):
+            existing = self.retrieve(params, "value")
+            if existing == "true":
+                result = True
+
+        return result
+
+    #=================================== Storage Key ========================================
+    def getStorageKey(self, params={}, type="", item={}):
+        self.common.log(repr(params), 5)
+        if type == "value":
+            return self._getValueStorageKey(params, item)
+        elif type == "viewmode":
+            return self._getViewModeStorageKey(params, item)
+        elif type == "thumbnail":
+            return self._getThumbnailStorageKey(params, item)
+        return self._getResultSetStorageKey(params)
+
+    def _getThumbnailStorageKey(self, params={}, item={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+        iget = item.get
+        key = ""
+
+        if get("search") or iget("search"):
+            key = "disco_search_"
+            if get("feed") or iget("feed"):
+                key = "search_"
+
+            if get("store") == "searches":
+                key = "search_"
+
+            if get("search"):
+                key += urllib.unquote_plus(get("search", ""))
+
+            if iget("search"):
+                key += urllib.unquote_plus(iget("search", ""))
+
+        if get("artist") or iget("artist"):
+            key = "artist_"
+
+            if get("artist"):
+                key += get("artist")
+
+            if iget("artist"):
+                key += iget("artist")
+
+        if get("user_feed"):
+            key = get("user_feed")
+
+            if get("channel"):
+                key = "subscriptions_" + get("channel")
+
+            if iget("channel"):
+                key = "subscriptions_" + iget("channel")
+
+            if get("playlist"):
+                key = "playlist_" + get("playlist")
+
+            if iget("playlist"):
+                key = "playlist_" + iget("playlist")
+
+        if key:
+            key += "_thumb"
+
+        return key.encode("utf-8","ignore")
+
+    def _getValueStorageKey(self, params={}, item={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+        iget = item.get
+        key = ""
+
+        if ((get("action") == "reverse_order" or get("user_feed") == "playlist") and (iget("playlist") or get("playlist"))):
+
+            key = "reverse_playlist_"
+            if iget("playlist"):
+                key += iget("playlist")
+
+            if get("playlist"):
+                key += get("playlist")
+
+            if (get("external")):
+                key += "_external_" + get("contact")
+        return key 
+
+    def _getViewModeStorageKey(self, params={}, item={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+        iget = item.get
+        key = ""
+
+        if (get("external")):
+            key = "external_" + get("contact") + "_"
+        elif (iget("external")):
+            key = "external_" + iget("contact") + "_"
+
+        if get("channel"):
+            key += "view_mode_" + get("channel")
+        elif (iget("channel")):
+            key += "view_mode_" + iget("channel")
+
+        return key
+
+    def _getResultSetStorageKey(self, params={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+
+        key = ""
+
+        if get("scraper"):
+            key = "s_" + get("scraper")
+
+            if get("scraper") == "music_artist" and get("artist"):
+                key += "_" + get("artist")
+
+            if get("scraper") == "disco_search":
+                key = "store_disco_searches"
+
+            if get("category"):
+                key += "_category_" + get("category")
+
+            if get("show"):
+                key += "_" + get("show")
+                key += "_season_" + get("season", "0")
+        if get("user_feed"):
+            key = "result_" + get("user_feed")
+
+            if get("playlist"):
+                key += "_" + get("playlist")
+
+            if get("channel"):
+                key += "_" + get("channel")
+
+            if get("external") and not get("thumb"):
+                key += "_external_" + get("contact")
+
+        if get("feed") == "search":
+            key = "store_searches"
+
+        if get("store"):
+            key = "store_" + get("store")
+
+        self.common.log("Done : %s" % key, 5)
+        return key
+
+    #============================= Storage Functions =================================
+    def store(self, params={}, results=[], type="", item={}):
+        self.common.log(repr(params), 5)
+        key = self.getStorageKey(params, type, item)
+
+        self.common.log("Got key " + repr(key))
+
+        self.common.log(repr(type), 5)
+        if type == "thumbnail" or type == "viewmode" or type == "value":
+            self.storeValue(key, results)
+        else:
+            self.storeResultSet(key, results)
+        self.common.log("done", 5)
+
+    def storeValue(self, key, value):
+        self.common.log(repr(key) + " - " + repr(value), 5)
+        if value:
+            self.cache.set(key, value)
+        self.common.log("done", 5)
+
+    def storeResultSet(self, key, results=[], params={}):
+        self.common.log(repr(params), 5)
+        get = params.get
+
+        if results:
+            if get("prepend"):
+                searchCount = (10, 20, 30, 40,)[int(self.settings.getSetting("saved_searches"))]
+                existing = self.retrieveResultSet(key)
+                existing = [results] + existing[:searchCount]
+                self.cache.set(key, repr(existing))
+            elif get("append"):
+                existing = self.retrieveResultSet(key)
+                existing.append(results)
+                self.cache.set(key, repr(existing))
+            else:
+                self.common.log("hit else", 5)
+                value = repr(results)
+                self.common.log(repr(key) + " - " + repr(value), 5)
+                self.cache.set(key, value)
+        self.common.log("done", 5)
+
+    def storeSettings(self, params={}, results=[], type="", item={}):
+        self.common.log(repr(params), 5)
+
+        key = self.getStorageKey(params, type, item)
+
+        self.storeResultSetSettings(key, results)
+
+        self.common.log("done", 5)
+
+    def storeResultSetSettings(self, key, results=[], params={}):
+        self.common.log(repr(params), 5)
+
+        if results:
+            value = repr(results)
+            self.common.log(repr(key) + " - " + repr(value), 5)
+            self.settings.setSetting(key, value)
+
+        self.common.log("done", 5)
+
+    #============================= Retrieval Functions =================================
+    def retrieve(self, params={}, type="", item={}):
+        self.common.log(repr(params), 5)
+        key = self.getStorageKey(params, type, item)
+
+        self.common.log("Got key " + repr(key))
+
+        if type == "thumbnail" or type == "viewmode" or type == "value":
+            return self.retrieveValue(key)
+        else:
+            return self.retrieveResultSet(key)
+
+    def retrieveValue(self, key):
+        self.common.log(repr(key), 5)
+        value = ""
+        if key:
+            value = self.cache.get(key)
+
+        return value
+
+    def retrieveResultSet(self, key):
+        self.common.log(repr(key), 5)
+        results = []
+
+        value = self.cache.get(key)
+        self.common.log(repr(value), 5)
+        if value:
+            try:
+                results = eval(value)
+            except:
+                results = []
+
+        return results
+
+    def retrieveSettings(self, params={}, type="", item={}):
+        self.common.log(repr(params), 5)
+        key = self.getStorageKey(params, type, item)
+
+        self.common.log("Got key " + repr(key))
+
+        return self.retrieveResultSetSettings(key)
+
+    def retrieveResultSetSettings(self, key):
+        self.common.log(repr(key), 5)
+        results = []
+
+        value = self.settings.getSetting(key)
+        self.common.log(repr(value), 5)
+        if value:
+            try:
+                results = eval(value)
+            except:
+                results = []
+
+        return results
+
+    def updateVideoIdStatusInCache(self, pre_id, ytobjects):
+        self.common.log(pre_id)
+        save_data = {}
+        for item in ytobjects:
+            if "videoid" in item:
+                save_data[item["videoid"]] = repr(item)
+
+        self.cache.setMulti(pre_id, save_data)
+
+    def getVideoIdStatusFromCache(self, pre_id, ytobjects):
+        self.common.log(pre_id)
+        load_data = []
+        for item in ytobjects:
+            if "videoid" in item:
+                load_data.append(item["videoid"])
+
+        res = self.cache.getMulti(pre_id, load_data)
+        if len(res) != len(load_data):
+            self.common.log("Length mismatch:" + repr(res) + " - " + repr(load_data))
+
+        i = 0
+        for item in ytobjects:
+            if "videoid" in item:
+                if i < len(res):
+                    item["Overlay"] = res[i]
+                i += 1 # This can NOT be enumerated because there might be missing videoids
+        return ytobjects
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/YouTubeSubtitleControl.py	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,391 @@
+'''
+   YouTube plugin for XBMC
+    Copyright (C) 2010-2012 Tobias Ussing And Henrik Mosgaard Jensen
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import urllib
+import re
+import os.path
+import time
+try: import simplejson as json
+except ImportError: import json
+
+
+class YouTubeSubtitleControl():
+
+    urls = {}
+    urls['timed_text_index'] = "http://www.youtube.com/api/timedtext?type=list&v=%s"
+    urls['close_caption_url'] = "http://www.youtube.com/api/timedtext?type=track&v=%s&lang=%s"
+    urls['annotation_url'] = "http://www.youtube.com/annotations/read2?video_id=%s&feat=TC"
+
+    def __init__(self):
+        self.xbmc = sys.modules["__main__"].xbmc
+        self.xbmcgui = sys.modules["__main__"].xbmcgui
+        self.xbmcvfs = sys.modules["__main__"].xbmcvfs
+
+        self.common = sys.modules["__main__"].common
+        self.utils = sys.modules["__main__"].utils
+        self.core = sys.modules["__main__"].core
+
+        self.dbg = sys.modules["__main__"].dbg
+        self.settings = sys.modules["__main__"].settings
+        self.storage = sys.modules["__main__"].storage
+
+    # ================================ Subtitle Downloader ====================================
+    def downloadSubtitle(self, video={}):
+        self.common.log(u"")
+        get = video.get
+
+        style = u""
+        result = u""
+
+        if self.settings.getSetting("annotations") == "true" and not "downloadPath" in video:
+            xml = self.core._fetchPage({"link": self.urls["annotation_url"] % get('videoid')})
+            if xml["status"] == 200 and xml["content"]:
+                (result, style) = self.transformAnnotationToSSA(xml["content"])
+
+        if self.settings.getSetting("lang_code") != "0":
+            subtitle_url = self.getSubtitleUrl(video)
+
+            if not subtitle_url and self.settings.getSetting("transcode") == "true":
+                subtitle_url = self.getTranscriptionUrl(video)
+
+            if subtitle_url:
+                xml = self.core._fetchPage({"link": subtitle_url})
+                if xml["status"] == 200 and xml["content"]:
+                    result += self.transformSubtitleXMLtoSRT(xml["content"])
+
+        if len(result) > 0:
+            result = "[Script Info]\r\n; This is a Sub Station Alpha v4 script.\r\n; For Sub Station Alpha info and downloads,\r\n; go to http://www.eswat.demon.co.uk/\r\n; or email kotus@eswat.demon.co.uk\r\nTitle: Auto Generated\r\nScriptType: v4.00\r\nCollisions: Normal\r\nPlayResY: 1280\r\nPlayResX: 800\r\n\r\n[V4 Styles]\r\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding\r\nStyle: Default,Arial,80,&H00FFFFFF&,65535,65535,&00000000&,-1,0,1,3,2,2,0,0,0,0,0\r\nStyle: speech,Arial,60,0,65535,65535,&H4BFFFFFF&,0,0,3,1,0,1,0,0,0,0,0\r\nStyle: popup,Arial,60,0,65535,65535,&H4BFFFFFF&,0,0,3,3,0,1,0,0,0,0,0\r\nStyle: highlightText,Wolf_Rain,60,15724527,15724527,15724527,&H4BFFFFFF&,0,0,1,1,2,2,5,5,0,0,0\r\n" + style + "\r\n[Events]\r\nFormat: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n" + result
+
+            result += "Dialogue: Marked=0,0:00:0.00,0:00:0.00,Default,Name,0000,0000,0000,,\r\n"  # This solves a bug.
+            self.saveSubtitle(result, video)
+            self.common.log(u"Done")
+            return True
+
+        self.common.log(u"Failure")
+        return False
+
+    def getSubtitleUrl(self, video={}):
+        self.common.log(u"")
+        get = video.get
+        url = ""
+
+        xml = self.core._fetchPage({"link": self.urls["timed_text_index"] % get('videoid')})
+
+        self.common.log(u"subtitle index: " + repr(xml["content"]))
+        self.common.log(u"CONTENT TYPE1: " + repr(type(xml["content"])))
+
+        if xml["status"] == 200:
+            subtitle = ""
+            code = ""
+            codelist = self.common.parseDOM(xml["content"], "track", ret="lang_code")
+            sublist = self.common.parseDOM(xml["content"], "track", ret="name")
+            lang_original = self.common.parseDOM(xml["content"], "track", ret="lang_original")
+            if len(sublist) != len(codelist) and len(sublist) != len(lang_original):
+                self.common.log(u"Code list and sublist length mismatch: " + repr(codelist) + " - " + repr(sublist))
+                return ""
+
+            if len(codelist) > 0:
+                # Fallback to first in list.
+                subtitle = sublist[0].replace(u" ", u"%20")
+                code = codelist[0]
+
+            lang_code = ["off", "en", "es", "de", "fr", "it", "ja"][int(self.settings.getSetting("lang_code"))]
+            self.common.log(u"selected language: " + repr(lang_code))
+            if True:
+                for i in range(0, len(codelist)):
+                    data = codelist[i].lower()
+                    if data.find("-") > -1:
+                        data = data[:data.find("-")]
+
+                    if codelist[i].find(lang_code) > -1:
+                        subtitle = sublist[i].replace(" ", "%20")
+                        code = codelist[i]
+                        self.common.log(u"found subtitle specified: " + subtitle + " - " + code)
+                        break
+
+                    if codelist[i].find("en") > -1:
+                        subtitle = sublist[i].replace(" ", "%20")
+                        code = "en"
+                        self.common.log(u"found subtitle default: " + subtitle + " - " + code)
+
+            if code:
+                url = self.urls["close_caption_url"] % (get("videoid"), code)
+                if len(subtitle) > 0:
+                    url += "&name=" + subtitle
+
+
+        self.common.log(u"found subtitle url: " + repr(url))
+        return url
+
+    def getSubtitleFileName(self, video):
+        get = video.get
+        lang_code = ["off", "en", "es", "de", "fr", "it", "ja"][int(self.settings.getSetting("lang_code"))]
+        filename = ''.join(c for c in self.common.makeUTF8(video['Title']) if c not in self.utils.INVALID_CHARS) + "-[" + get('videoid') + "]-" + lang_code.upper() + ".ssa"
+        filename = filename.encode("ascii", "ignore")
+        return filename
+
+    def saveSubtitle(self, result, video={}):
+        self.common.log(repr(type(result)))
+        filename = self.getSubtitleFileName(video)
+
+        path = os.path.join(self.xbmc.translatePath(self.settings.getAddonInfo("profile")).decode("utf-8"), filename)
+
+        w = self.storage.openFile(path, "w")
+        try:
+            w.write(result.encode("utf-8")) # WTF, didn't have to do this before, did i?
+        except:
+            w.write(result)
+            self.common.log(u"NOT utf-8 WRITE!!!: " + path + " - " + repr(result))
+            time.sleep(20)
+
+        w.close()
+
+        if "downloadPath" in video:
+            self.xbmcvfs.rename(path, os.path.join(video["downloadPath"], filename))
+
+
+    def getTranscriptionUrl(self, video={}):
+        self.common.log(u"")
+        get = video.get
+        trans_url = ""
+        if "ttsurl" in video:
+            if len(video["ttsurl"]) > 0:
+                trans_url = urllib.unquote(video["ttsurl"]).replace("\\", "") + "&type=trackformat=1&lang=en&kind=asr&name=&v=" + get("videoid")
+                if self.settings.getSetting("lang_code") > 1:  # 1 == en
+                    lang_code = ["off", "en", "es", "de", "fr", "it", "ja"][int(self.settings.getSetting("lang_code"))]
+                    trans_url += "&tlang=" + lang_code
+        return trans_url
+
+    def simpleReplaceHTMLCodes(self, str):
+        str = str.strip()
+        str = str.replace("&amp;", "&")
+        str = str.replace("&quot;", '"')
+        str = str.replace("&hellip;", "...")
+        str = str.replace("&gt;", ">")
+        str = str.replace("&lt;", "<")
+        str = str.replace("&#39;", "'")
+        return str
+
+    def convertSecondsToTimestamp(self, seconds):
+        self.common.log(u"", 3)
+        hours = str(int(seconds / 3600))
+        seconds = seconds % 3600
+
+        minutes = str(int(seconds/60))
+        if len(minutes) == 1:
+            minutes = "0" + minutes
+
+        seconds = str(seconds % 60)
+        if len(seconds) == 1:
+            seconds = "0" + seconds
+
+        self.common.log(u"Done", 3)
+        return "%s:%s:%s" % (hours, minutes, seconds)
+
+    def transformSubtitleXMLtoSRT(self, xml):
+        self.common.log(u"")
+
+        result = u""
+        for node in self.common.parseDOM(xml, "text", ret=True):
+            text = self.common.parseDOM(node, "text")[0]
+            text = self.simpleReplaceHTMLCodes(text).replace("\n", "\\n")
+            start = float(self.common.parseDOM(node, "text", ret="start")[0])
+            duration = self.common.parseDOM(node, "text", ret="dur")
+            end = start + 0.1
+            if len(duration) > 0:
+                end = start + float(duration[0])
+
+            start = self.convertSecondsToTimestamp(start)
+            end = self.convertSecondsToTimestamp(end)
+
+            if start and end:
+                result += "Dialogue: Marked=%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\r\n" % ("0", start, end, "Default", "Name", "0000", "0000", "0000", "", text)
+
+        return result
+
+    def transformColor(self, color):
+        self.common.log(u"Color: %s - len: %s" % (color, len(color)), 3)
+        if color:
+            color = hex(int(color))
+            color = str(color)
+            color = color[2:]
+            self.common.log(u"Color: %s - len: %s" % (color, len(color)), 5)
+            if color == "0":
+                color = "000000"
+            if len(color) == 4:
+                color = "00" + color
+            if len(color) == 6:
+                color = color[4:6] + color[2:4] + color[0:2]
+
+        self.common.log(u"Returning color: %s - len: %s" % (color, len(color)), 5)
+        return color
+
+    def transformAlpha(self, alpha):
+        self.common.log(u"Alpha: %s - len: %s" % (alpha, len(alpha)), 5)
+        if not alpha or alpha == "0" or alpha == "0.0":
+            alpha = "-1"  # No background.
+        else:
+            # YouTube and SSA have inverted alphas.
+            alpha = int(float(alpha) * 256)
+            alpha = hex(256 - alpha)
+            alpha = alpha[2:]
+
+        self.common.log(u"Alpha: %s - len: %s" % (alpha, len(alpha)), 5)
+        return alpha
+
+    def transformAnnotationToSSA(self, xml):
+        self.common.log(u"")
+        result = u""
+        ssa_fixes = []
+        style_template = u"Style: annot%s,Arial,%s,&H%s&,&H%s&,&H%s&,&H%s&,0,0,3,3,0,1,0,0,0,0,0\r\n"
+        styles_count = 0
+        append_style = u""
+        entries = self.common.parseDOM(xml, "annotation", ret=True)
+        for node in entries:
+            if node:
+                stype = u"".join(self.common.parseDOM(node, "annotation", ret="type"))
+                style = u"".join(self.common.parseDOM(node, "annotation", ret="style"))
+                self.common.log(u"stype : " + stype, 5)
+                self.common.log(u"style : " + style, 5)
+
+                if stype == "highlight":
+                    linkt = "".join(self.common.parseDOM(node, "url", ret="type"))
+                    linkv = "".join(self.common.parseDOM(node, "url", ret="value"))
+                    if linkt == "video":
+                        self.common.log(u"Reference to video : " + linkv)
+                elif node.find("TEXT") > -1:
+                    text = self.common.parseDOM(node, "TEXT")
+                    if len(text):
+                        text = self.common.replaceHTMLCodes(text[0])
+                        start = ""
+
+                        ns_fsize = 60
+                        start = False
+                        end = False
+
+                        if style == "popup":
+                            cnode = self.common.parseDOM(node, "rectRegion", ret="t")
+                            start = cnode[0]
+                            end = cnode[1]
+                            tmp_y = self.common.parseDOM(node, "rectRegion", ret="y")
+                            tmp_h = self.common.parseDOM(node, "rectRegion", ret="h")
+                            tmp_x = self.common.parseDOM(node, "rectRegion", ret="x")
+                            tmp_w = self.common.parseDOM(node, "rectRegion", ret="w")
+                        elif style == "speech":
+                            cnode = self.common.parseDOM(node, "anchoredRegion", ret="t")
+                            start = cnode[0]
+                            end = cnode[1]
+                            tmp_y = self.common.parseDOM(node, "anchoredRegion", ret="y")
+                            tmp_h = self.common.parseDOM(node, "anchoredRegion", ret="h")
+                            tmp_x = self.common.parseDOM(node, "anchoredRegion", ret="x")
+                            tmp_w = self.common.parseDOM(node, "anchoredRegion", ret="w")
+                        elif style == "higlightText":
+                            cnode = False
+                        else:
+                            cnode = False
+
+                        for snode in self.common.parseDOM(node, "appearance", attrs={"fgColor": ".*?"}, ret=True):
+                            ns_fsize = self.common.parseDOM(snode, "appearance", ret="textSize")
+                            if len(ns_fsize):
+                                ns_fsize = int(1.2 * (1280 * float(ns_fsize[0]) / 100))
+                            else:
+                                ns_fsize = 60
+                            ns_fcolor = self.common.parseDOM(snode, "appearance", ret="fgColor")
+                            ns_fcolor = self.transformColor(ns_fcolor[0])
+
+                            ns_bcolor = self.common.parseDOM(snode, "appearance", ret="bgColor")
+                            ns_bcolor = self.transformColor(ns_bcolor[0])
+
+                            ns_alpha = self.common.parseDOM(snode, "appearance", ret="bgAlpha")
+                            ns_alpha = self.transformAlpha(ns_alpha[0])
+
+                            append_style += style_template % (styles_count, ns_fsize, ns_fcolor, ns_fcolor, ns_fcolor, ns_alpha + ns_bcolor)
+                            style = "annot" + str(styles_count)
+                            styles_count += 1
+
+                        self.common.log(u"start: %s - end: %s - style: %s" % (start, end, style), 5)
+                        if start and end and style != "highlightText":
+                            marginV = 1280 * float(tmp_y[0]) / 100
+                            marginV += 1280 * float(tmp_h[0]) / 100
+                            marginV = 1280 - int(marginV)
+                            marginV += 5
+                            marginL = int((800 * float(tmp_x[0]) / 100))
+                            marginL += 5
+                            marginR = 800 - marginL - int((800 * float(tmp_w[0]) / 100)) - 15
+                            if marginR < 0:
+                                marginR = 0
+                            result += "Dialogue: Marked=%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\r\n" % ("0", start, end, style, "Name", marginL, marginR, marginV, "", text)
+                            ssa_fixes.append([start, end])
+
+        # Fix errors in the SSA specs.
+        if len(ssa_fixes) > 0:
+            for a_start, a_end in ssa_fixes:
+                for b_start, b_end in ssa_fixes:
+                    if time.strptime(a_end[0:a_end.rfind(".")], "%H:%M:%S") < time.strptime(b_start[0:b_start.rfind(".")], "%H:%M:%S"):
+                        result += "Dialogue: Marked=0,%s,%s,Default,Name,0000,0000,0000,,\r\n" % (a_end, b_start)
+
+        self.common.log(u"Done : " + repr((result, append_style)),5)
+        return (result, append_style)
+
+    def addSubtitles(self, video={}):
+        get = video.get
+        self.common.log(u"fetching subtitle if available")
+
+        filename = self.getSubtitleFileName(video)
+
+        download_path = os.path.join(self.settings.getSetting("downloadPath").decode("utf-8"), filename)
+        path = os.path.join(self.xbmc.translatePath(self.settings.getAddonInfo("profile")).decode("utf-8"), filename)
+
+        set_subtitle = False
+        if self.xbmcvfs.exists(download_path):
+            path = download_path
+            set_subtitle = True
+        elif self.xbmcvfs.exists(path):
+            set_subtitle = True
+        elif self.downloadSubtitle(video):
+            set_subtitle = True
+
+        self.common.log(u"Done trying to locate: " + path, 4)
+        if self.xbmcvfs.exists(path) and not "downloadPath" in video and set_subtitle:
+            player = self.xbmc.Player()
+
+            i = 0
+            while not player.isPlaying():
+                i += 1
+                self.common.log(u"Waiting for playback to start ")
+                time.sleep(1)
+                if i > 10:
+                    break
+
+            self.xbmc.Player().setSubtitles(path)
+            self.common.log(u"added subtitle %s to playback" % path)
+
+    def getLocalFileSource(self, params, video):
+        get = params.get
+        result = u""
+        if (get("action", "") != "download"):
+            path = self.settings.getSetting("downloadPath")
+            filename = u"".join(c for c in self.common.makeUTF8(video['Title']) if c not in self.utils.INVALID_CHARS) + u"-[" + get('videoid') + u"]" + u".mp4"
+            path = os.path.join(path.decode("utf-8"), filename)
+            try:
+                if self.xbmcvfs.exists(path):
+                    result = path
+            except:
+                self.common.log(u"failed to locate local subtitle file, trying youtube instead")
+        return result
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/YouTubeUtils.py	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,100 @@
+'''
+   YouTube plugin for XBMC
+   Copyright (C) 2010-2012 Tobias Ussing And Henrik Mosgaard Jensen
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import os
+
+
+class YouTubeUtils:
+    def __init__(self):
+        self.xbmc = sys.modules["__main__"].xbmc
+        self.settings = sys.modules["__main__"].settings
+        self.language = sys.modules["__main__"].language
+        self.common = sys.modules["__main__"].common
+        self.plugin = sys.modules["__main__"].plugin
+        self.dbg = sys.modules["__main__"].dbg
+        self.PR_VIDEO_QUALITY = self.settings.getSetting("pr_video_quality") == "true"
+        self.INVALID_CHARS = "\\/:*?\"<>|"
+        self.THUMBNAIL_PATH = os.path.join(self.settings.getAddonInfo('path'), "thumbnails")
+
+    def showMessage(self, heading, message):
+        self.common.log(repr(type(heading)) + " - " + repr(type(message)))
+        duration = ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10][int(self.settings.getSetting('notification_length'))]) * 1000
+        self.xbmc.executebuiltin((u'XBMC.Notification("%s", "%s", %s)' % (heading, message, duration)).encode("utf-8"))
+
+    def getThumbnail(self, title):
+        if (not title):
+            title = "DefaultFolder"
+
+        thumbnail = os.path.join(sys.modules["__main__"].plugin, title + ".png")
+
+        if (not self.xbmc.skinHasImage(thumbnail)):
+            thumbnail = os.path.join(self.THUMBNAIL_PATH, title + ".png")
+            if (not os.path.isfile(thumbnail)):
+                thumbnail = "DefaultFolder.png"
+
+        return thumbnail
+
+    def showErrorMessage(self, title="", result="", status=500):
+        if title == "":
+            title = self.language(30600)
+        if result == "":
+            result = self.language(30617)
+
+        if (status == 303):
+            self.showMessage(title, result)
+        else:
+            self.showMessage(title, self.language(30617))
+
+    def buildItemUrl(self, item_params={}, url=""):
+        blacklist = ("path", "thumbnail", "Overlay", "icon", "next", "content", "editid", "summary", "published", "count", "Rating", "Plot", "Title", "new_results_function")
+        for key, value in item_params.items():
+            if key not in blacklist:
+                url += key + "=" + value + "&"
+        return url
+
+    def addNextFolder(self, items=[], params={}):
+        get = params.get
+        item = {"Title": self.language(30509), "thumbnail": "next", "next": "true", "page": str(int(get("page", "0")) + 1)}
+        for k, v in params.items():
+            if (k != "thumbnail" and k != "Title" and k != "page" and k != "new_results_function"):
+                item[k] = v
+        items.append(item)
+
+    def extractVID(self, items):
+        if isinstance(items, str):
+            items = [items]
+
+        self.common.log(repr(items), 4)
+
+        ret_list = []
+        for item in items:
+            item = item[item.find("v=") + 2:]
+            if item.find("&") > -1:
+                item = item[:item.find("&")]
+            ret_list.append(item)
+
+        self.common.log(repr(ret_list), 4)
+        return ret_list
+
+    def convertStringToBinary(self, value):
+        if isinstance(value, unicode):
+            return ''.join(format(ord(i),'0>8b') for i in value)
+        else :
+            return value
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/addon.xml	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,22 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
+<addon id='plugin.video.youtube' version='3.4.0' name='YouTube' provider-name='TheCollective'>
+  <requires>
+    <import addon='xbmc.python' version='2.0'/>
+    <import addon='script.module.simplejson' version='2.0.10'/>
+    <import addon='script.common.plugin.cache' version='1.5.0'/>
+    <import addon='script.module.parsedom' version='1.5.1'/>
+    <import addon='script.module.simple.downloader' version='0.9.4'/>
+  </requires>
+  <extension point='xbmc.python.pluginsource' library='default.py'>
+        <provides>video</provides>
+  </extension>
+  <extension point='xbmc.addon.metadata'>
+    <platform>all</platform>
+    <summary lang='en'>YouTube video plugin</summary>
+    <description lang='en'>Plugin that lets you browse and play videos from everybody's favorite video site!</description>
+    <disclaimer lang='en'>Some parts of this addon may not be legal in your country of residence - please check with your local laws before installing.</disclaimer>
+    <summary lang='bg'>Видео добавка за YouTube</summary>
+    <description lang='bg'>Добавката ви позволява да разглеждате и гледате видео клипове от любимия на на всички видео сайт!</description>
+    <disclaimer lang='bg'>Някои части от добавката може да са незаконни в държавата, в която се намирате - моля, проверете местните закони, преди да я инсталирате.</disclaimer>
+  </extension>
+</addon>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/changelog.txt	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,346 @@
+[B]TODO:[/B]
+- Fix RTMP support.
+- Unit test new functions in storage...
+- Replace scraper with feeds for: shows.
+- Integration tests on all user actions.
+- Use extractJS and urlparse.parse_qs.
+- UTF8/16 does not work consistently(Verify failures against minidom implementation)
+- Embed playback fallback
+
+[B]Errata[/B]
+- [XBMC] Thumbnails sometimes turns into black box or Folder (XBMC not detecting when thumbnail is set and defaulting to icon?)
+- [XBMC] When sorting items, it's impossible to get them to return to their original order
+- [XBMC] Has Excessive Memory use after running the plugin for prolonged periods of time
+- [RTMPDUMP] Doesn't support handshake type 10 which is required by youtube.
+
+[B]Version 3.4.0[/B]
+- Fixed Login was broken after youtube changed look
+- Fixed Playback of over 18 videos was broken with the new youtube look
+- Fixed User scraper for my liked videos
+- Fixed scraper for Top 100 Trailers, Youtube weekly Top 100 Music videos, and Youtube Disco
+
+[B]Version 3.3.0[/B]
+- Fixed playback of over 18 videos, should now work if the user is logged in.
+- Fixed bug where playback or video listing would fail due to bug in cache
+- Fixed problem in core that would sometimes break playback
+- Fixed problem in player that would cause hard crash when playback stream isn't available
+- Disabled movie section since it seems completely broken on the home page
+- Disabled show section since it seems completely broken on the home page
+- Added new scraper for VISO trailers which seems to have replace the old trailers page.
+
+[B]Version 3.2.0[/B]
+- Fixed playback
+- Minor refactoring to increase testability
+
+[B]Version 3.1.0[/B]
+- Fixed login
+- Fixed crash when listing subscriptions with non ascii chars
+- Fixed subtitle parser which was broken by the move to parseDOM
+- Fixed various scraper integration test failing cause by youtube site changes
+
+[B]Version 3.0.0[/B]
+- Fixed show scraper since it was broken in a recent redesign
+- Disabled trailers scraper since functionality is gone from YouTubes site
+- Fixed Login code broken by YouTube redesign.
+- Change from minidom to parsedom(Should speedup raspberry PI)
+
+[B]Version 2.9.2[/B]
+- Listing of folders with more than 50 items was broken
+- Listing subscriptions was broken by YouTube's sudden api changes.
+
+[B]Version 2.9.1[/B]
+- Fixed indentation error that broke pagination of long playlists and subscription lists, basically any folder list with more than 50 elements was broken.
+
+[B]Version 2.9.0[/B]
+- Sanity check feeds -> listAll temp_objects, since it (randomly?) fails
+- Upgraded dependencies
+- Changed Merged Music and disco since YouTube removed almost all disco features
+- Changed Removed disco top artists since YouTube Removed it from their site
+- Added Scraper for the users watched history feed
+- Report SSL errors back to the user
+- Fixed page 2 on subscriptions.
+
+[B]Version 2.8.0[/B]
+- Changed All videos now have a unique path from xbmc's perspective even though they're present in different folders.
+- Changed Replaced the categories scraper with api calls
+- Moved some functions from youtube utils to common so they can be shared between different plugins
+- Fixed music artist and top hit's scraper
+- Added play all option to youtube top 100
+- Added setting to control playback of 3d anaglyph streams 
+- Changed downloader into a separate service.
+
+[B]Version 2.7.0 (Beta)[/B]
+- Added new sqlite based caching system with script.common.plugin.cache
+- Added support for unlinked accounts
+- Added unit test suite of plugin
+- Added integration test suite of plugins main functions including full scraper coverage.
+- Added Caching module to reduce the traffic load on youtube.
+- Fixed Downloads messages.
+- Changed scrapers to use custom regex based parsing framework instead of BeautifullSoup since this greatly improved scraper speed.
+- Changed default video streams up to 720p to h264 since apple tv doesn't handle VP8 well.
+- Changed switched Watch Later to api feed removing a scraper
+- Changed switched YouTube Suggests to api feed removing a scraper
+- Changed Reworked Regex based scraper framework to be more reliable and easier to use
+- Changed Improved Dharma compatability by adding dummy xbmcvfs
+- Changed Download progress bar now auto hides when playing a videos.
+- Changed Remove legacy download and moved windows to new download system.
+- Changed moved from file based locking to new storage service based lock
+
+[B]Version 2.6.0 (Beta)[/B]
+- Added New playback function. Now uses unified code accross all pages(html scraping, embeded, flash fallback). Supports fallback server.
+- Added fallback for age restricted videos (Will fail if embedding is disabled)
+- Added support for locally stored artists in Youtube Music
+- Added support for play all on My Liked Videos
+- Added support for play all on YouTube suggests
+- Added support for play all on Music Artist
+- Added support for play all on My Watch Later
+- Added Support for Oauth2 authentication
+- Added Suppert for 2 factor authentication
+- Added Support OAuth2 token refresh when login token expires (reducing the need to re-login all the time) 
+- Added support for YouTube Annotations to subtitles
+- Added PlayAll support for artist listings
+- Added Support for enabling and disabling youtube caching servers (aka o-preffered-yourisp urls)
+- Changed YouTube live scraper to use API feed instead, this nearly doubled the amount of live events. 
+- Changed Plugin now honors YouTube rules for use of their batch api which sould prevent "Unkown Title" errors from batch queries 
+- Changed Subtitle format to SSA to support merging of annotations and subtitles
+- Changed Caching of playlists, to support playlist editid accross all pages
+- Changed Scraper to use custom dom parsing with regexe's as it greatly improves speed 
+- Changed Caching to a common storage system
+- Removed old outdated translations
+- Fixed playback after youtube changes
+- Fixed RTMPe support (we now provide playpath correctly) - NOTE: this requires and updated version of librtmp
+
+[B]Version 2.5.0 (Beta)[/B]
+- Added new option to select playback and download quality with dialogs
+- Added Caching and alphabetically sorting of My playlists, My contacts and My subscriptions 
+- Added "Play all" context menu on certain folders 
+- Added "Shuffle and Play All" Context menu on certain folders
+- Added "Play All from video position in certain folders (ie. playlists and other non-infinite folders)
+- Added Playback of YouTube Live streams 
+- Added Caching of playlist and option to reverse playlist order
+- Added playback of WebM streams if user enables it on his/her profile
+- Added Date for sorting
+- Added "Auto-magic" re-login on token verfication failure
+- Added 2 new feeds: "most shared" and "trending videos" that gives a better "pulse" of youtube videos.
+- Added YouTube live scraper under explore youtube
+- Added YouTube Top 100 music chart scraper
+- Added YouTube Music and Artist scraper
+- Added YouTube My Watch Later scraper
+- Added YouTube My liked Video scraper
+- Added socket timeout since that makes now appears to work on windows
+- Added New download dialog showing queue and progress completion.
+- Added support for xbmcvfs in downloads. (ie. download to temp folder then let xbmc move to final location)
+- Added support for using embedded link to get video information. Better playback stability
+- Changed Login to use new Client Login url
+- Changed Playback function now shows a more user friendly error message on failure
+- Changed Download function now shows a progress-bar (OSX and Linux only)
+- Changed Download function will now only download one file at a time and queues multiple files (OSX and Linux only).
+- Changed "Disco Top 25" to "Disco Top 50" scraper 
+- Changed plugin will now play a video from file-system if the file is still in the download folder (Partially working as this has revealed XBMC bug.)
+- Changed more videos by user, and related videos so you no longer have to be logged in for them to work.
+- Refactor: Simplify playback code
+- Refactor: Large scale refactor to make code more maintainable
+- Fixed Age Verification for 18+ content was broken by YouYube. 
+- Fixed notifications on empty folders
+- Fixed Playback of very low resolution videos
+- Fixed scraper pagination
+- Fixed Disco scraper it was broken again.
+- Fixed add remove favorites
+- Fixed add remove subscription
+- Fixed broken Movies Scraper (from patch by chocol...@cpan.org)
+- Fixed problems when downloading a file to a network drives (thanks to ToCsIc...@gmail.com)
+- Fixed download subtitles when download a video
+- Fixed XBMC not refreshing folder after download completes.
+
+[B]Version 2.2.4[/B]
+- Verfied Compatibility with Eden - pre and submitted to repo.
+
+[B]Version 2.0.5[/B]
+- Changed Name string from Holland to The Netherlands (thanks to Syrion for pointing it out)
+
+[B]Version 2.0.4[/B]
+- Fixed YouTube character encoding changes broke disco scraper.
+- Fixed YouTube character encoding changes broke show scraper
+- Fixed Site changes broke Disco top 25 scraper
+- Fixed YouTube design changes broken categories scraper
+- Fixed Movies Scraper (backport of fix from new beta by chocol)
+- Fixed problems when downloading a file to a network drives (thanks to ToCsIc)
+- Fixed Age verification was broken, back working again.
+- Changed more videos by user, and related videos so you no longer have to be logged in for them to work.
+
+[B]Version 2.0.3[/B]
+- Fixed Context menus were "suddenly" missing on a lot of folder items (probably since pre v.2)
+- Fixed "New Subscriptions Videos" feed would show up on every subscriptions page this has been corrected
+- Fixed YouTube changed character encoding of their webpage which messed up playback in the plugin.
+- Added Portuguese (Brazil) translation courtesy of fschnack
+
+[B]Version 2.0.2[/B]
+- Fixed problem with "Now Playing" context menu item showing the Music Playlist and not the Video Playlist
+- Fixed Issue preventing the plugin running on XBMC builds using external python (ie. v2.7)
+- Fixed Issue where the Trailers scraper wouldn't allow you to see the last videos on the last page. 
+- Fixed issue where Show and movie Scrapers would fail due to assumptions about the number of videos on a page
+- Fixed issue where Show Scraper would miss the 1st video in every row after the first row
+- Fixed Unicode problems reported by vikjon0
+- Added Spanish translations courtesy of Jurrabi
+- Updated Licensing information
+
+[B]Version 2.0.1[/B]
+- Removed some unnecessary print's statements that were sadly included in production code, slowing the plugin down
+
+[B]Version 2.0.0[/B]
+- Version bump to Final Release
+- Updated Changelog
+- Changed Name to remove beta tag 
+
+[B]Version 1.9.6[/B]
+- Fixed zip file to make install from zip possible again, and reduced install size by 50%
+
+[B]Version 1.9.5[/B]
+- Update icon and thumbnail licensing
+- Fixed folder visibility settings for Explore YouTube Category
+- Allow user to select view modes between "Video Thumbnails", "Thumbnails Everywhere" and "XBMC Default" for listings
+- Added Upload Date and view count to video info pane 
+- Added "Related videos" to context menu
+- Added option to access Download folder from within the plugin 
+- Added support for viewing Subscription Playlists
+- Added queue folder option to newsubscription videos
+- Fixed Disco search after batch query JSON experimentation broke it
+- Fixed some minor errors in show scraper
+
+[B]Version 1.9.4[/B]
+- Add to favorites works again.
+- Restructured main folder structure to seperate scraper functions and feeds from user specific functions 
+- Changed download function to use Temporary filename while downloading, marking download as incomplete ex. video-incomplete.mp4 -> video.mp4
+- Added Scraper for YouTube Movies
+- Added icon for YouTube Movies
+- Updated icon for YouTube Trailers
+- Fixed Browse YouTube scraper (broken due to bad regex)
+- Switched to Thumbnail mode for tv show icons, cause it looks better
+- Batch processing functions now running against the official youtube API
+- Fixed Disco search (again) as YouTube broke it (again)... x2
+
+[B]Version 1.9.3[/B]
+- Improved api login
+- Improved http login
+- Added YouTube Show Scraper
+- Fixed Disco Search (YouTube broke it)
+- Fixed missing context menu items from folders everywhere..
+- Hardened handling of age restricted videos
+- No longer try login when no credentials have been provided.
+
+[B]Version 1.9.2[/B]
+- Disable socket timeout since that makes login fail on windows.
+
+[B]Version 1.9.1[/B]
+- Loop on 500, don't know how i missed this.
+- Minor cleanup
+
+[B]Version 1.9.0[/B]
+- Plugin can now confirm that the user is above 18 ( will only try if safe_search is disabled )
+- Added support for trailer thumbnails.
+- Added timeout option in advanced settings
+- Added sorting options (title, upload date, rating, runtime)
+- Added Scraper for YouTube categories (1 level only)
+- Added Scraper for YouTube Trailers
+- Added Scraper for YouTube Disco music searches
+- Added "More videos by User" option as requested by spiff
+- Added edit search feature, pops up with search in question and allows you to change it..
+- Added "Set content to movies" for video lists as this will allow the user to use other skin view modes
+- Added advanced setting to enable users to select playback quality pr video item in the context menu as requested by Nektarios
+- Added Nicer default icons for video items
+- Added Nicer default icons for searches
+- Added useragent to requests send by XBMC.
+- Enabled RTMP support in release code.
+- Reversed the "folders" settings tab ie Show Feeds (Enabled) instead of "Hide Feeds" (Disabled)
+- Refactored core functions for stability and readability.
+- Loop all url requests upto 5 times, should increase stability.
+- Refactor of navigation layer, hopefully simplifying maintainance
+- Removed unnessecary remapping of variable names between core and navigation
+- Fixed "Unknown Uploader" for some folders"
+- Added dummy video items in batch requests for when youtube removed an item.
+- Loop on 503: Service Unavailable errors.
+- Updated Browse Youtube to use regex.
+
+[B]Version 1.8.1[/B]
+- Enable RTMP support
+- Changed icon to conform to XBMC icon guidelines
+- Added disclaimer to addon xml (as requested by Team XBMC)
+
+[B]Version 1.8.0[/B]
+- Solved confusion caused by multiple licens files (removed GPL v2 license and renamed GPL v3 to LICENSE.txt)
+- Version bump to replace old YouTube plugin
+
+[B]Version 1.1.0[/B]
+- Version bump to Final Release
+
+[B]Version 0.9.9[/B]
+- Bugfixes
+- Cleanup
+- Errorhandling
+- Show/Hide folders
+- Fixed thumbnail bug
+- Added support for selection of download quality seperate from playback quality 
+- Added support thumbnail view on confluence
+- New download code supporting useragent.
+
+[B]Version 0.9.8[/B]
+- fix login message "bad username and password"
+- Always print reply from youtube on uncaught exceptions
+- Added steps to httpLogin to aid with debugging
+- Harden swfConfig
+- Only httpLogin when required
+- Minor fixes
+- Fix, download didn't work with & in the title
+- Loop httpLogin up to 10 times on connection failure.
+- Debug is no longer forced on.
+- Remove counter on login message
+
+[B]Version 0.9.7[/B]
+- Minor bugfixes
+- Improve messages
+- General cleanup
+- Hardening certain function.
+- Replaced some icons to fix license issues.
+- Add icon licenses file.
+- Tested against revision 33122 on linux(ubuntu and arch), Windows vista, and Snow Leopard.
+- Added login loop. Max 10 tries, 1 second delay.
+
+[B]Version 0.9.6[/B]
+- Fixed potential crash in Recommended core function
+- Fixed crash in Feeds when a video was marked private
+- Added more debug to rtmp calls
+- Added more debug to login
+- Added debug to uncaught exceptions through the entire core.
+- Fixed pageination in "Most Recent" and "Recently Featured"
+- Always have enabled debug.
+
+[B]Version 0.9.5[/B]
+- Added even more debug to login
+- Make login retry on IOERror failure
+- Removed some login headers.
+
+[B]Version 0.9.4[/B]
+- Added refinements of searches support
+- Added queue folder support
+- Fixed RTMP support
+- Implemented release disable support for RTMP
+- Added try/except to _extractVariables
+- Hopefully improved login debugging.
+
+[B]Version 0.9.3[/B]
+- Added better errorhandling of videos youtube deny access too
+- Actually enable debugging
+
+[B]Version 0.9.2[/B]
+- Added debug to login
+- Fixed playback of non playable item (settings)
+
+[B]Version 0.9.1[/B]
+- After showing the settings window on the first run it didn't log in.
+- Improved error messages related to login.
+- Added some more error handling.
+
+[B]Version 0.9[/B]
+- Cleanup of listFeedFolder
+- Cleanup of listUserFolder
+- Added Viewed and Downloaded overlay to video items.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/default.py	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,113 @@
+'''
+    YouTube plugin for XBMC
+    Copyright (C) 2010-2011 Tobias Ussing And Henrik Mosgaard Jensen
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import xbmc
+import xbmcplugin
+import xbmcaddon
+import xbmcgui
+import urllib2
+import cookielib
+try:
+    import xbmcvfs
+except ImportError:
+    import xbmcvfsdummy as xbmcvfs
+
+# plugin constants
+version = "3.4.0"
+plugin = "YouTube-" + version
+author = "TheCollective"
+url = "www.xbmc.com"
+
+# xbmc hooks
+settings = xbmcaddon.Addon(id='plugin.video.youtube')
+language = settings.getLocalizedString
+dbg = settings.getSetting("debug") == "true"
+dbglevel = 3
+
+# plugin structure
+feeds = ""
+scraper = ""
+playlist = ""
+navigation = ""
+downloader = ""
+storage = ""
+login = ""
+player = ""
+cache = ""
+
+cookiejar = cookielib.LWPCookieJar()
+cookie_handler = urllib2.HTTPCookieProcessor(cookiejar)
+opener = urllib2.build_opener(cookie_handler)
+
+if (__name__ == "__main__" ):
+    if dbg:
+        print plugin + " ARGV: " + repr(sys.argv)
+    else:
+        print plugin
+
+    try:
+        import StorageServer
+        cache = StorageServer.StorageServer("YouTube")
+    except:
+        import storageserverdummy as StorageServer
+        cache = StorageServer.StorageServer("YouTube")
+
+    import CommonFunctions as common
+    common.plugin = plugin
+
+    import YouTubeUtils
+    utils = YouTubeUtils.YouTubeUtils()
+    import YouTubeStorage
+    storage = YouTubeStorage.YouTubeStorage()
+    import YouTubePluginSettings
+    pluginsettings = YouTubePluginSettings.YouTubePluginSettings()
+    import YouTubeCore
+    core = YouTubeCore.YouTubeCore()
+    import YouTubeLogin
+    login = YouTubeLogin.YouTubeLogin()
+    import YouTubeFeeds
+    feeds = YouTubeFeeds.YouTubeFeeds()
+    import YouTubeSubtitleControl
+    subtitles = YouTubeSubtitleControl.YouTubeSubtitleControl()
+    import YouTubePlayer
+    player = YouTubePlayer.YouTubePlayer()
+    import SimpleDownloader as downloader
+    downloader = downloader.SimpleDownloader()
+    import YouTubeScraper
+    scraper = YouTubeScraper.YouTubeScraper()
+    import YouTubePlaylistControl
+    playlist = YouTubePlaylistControl.YouTubePlaylistControl()
+    import YouTubeNavigation
+    navigation = YouTubeNavigation.YouTubeNavigation()
+
+    if (not settings.getSetting("firstrun")):
+        login.login()
+        settings.setSetting("firstrun", "1")
+
+    if (not sys.argv[2]):
+        navigation.listMenu()
+    else:
+        params = common.getParameters(sys.argv[2])
+        get = params.get
+        if (get("action")):
+            navigation.executeAction(params)
+        elif (get("path")):
+            navigation.listMenu(params)
+        else:
+            print plugin + " ARGV Nothing done.. verify params " + repr(params)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/icon and thumbnail licensing.txt	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,155 @@
+icon.png
+Author: IconShock
+License: Free for commercial use
+http://www.iconfinder.com/icondetails/49221/256/youtube_icon
+
+download.png
+Author: Bobbyperux (MISTO icon pack)
+License: Freeware Non-commercial
+http://findicons.com/icon/85798/arrow_down?id=85798
+
+movies.png
+Author: Sebastiaan de With
+License: Freeware Non-commercial
+http://findicons.com/icon/35690/sidebar_movies_blue
+
+watch_later.png
+Author: Jack Cai - http://www.doublejdesign.co.uk/
+Siena - pack
+License: Creative Commons Attribution No Derivatives (by-nd)
+http://findicons.com/icon/177411/clock_green?width=256#
+
+history.png
+Author: The Iconfactory 
+Flurry System pack
+License: Freeware Non-commercial
+http://findicons.com/icon/131422/time_machine?width=256#
+
+
+add_user.png
+Modified version of User icon by VisualPharm, modified by HenrikDK
+Author: VisualPharm
+Vista icons by VisualPharm: http://www.visualpharm.com
+License: Linkware
+http://www.iconarchive.com/category/object/office-space-icons-by-visualpharm.html
+
+login.png
+Modified version of radium key icon
+Author: Sean Poon - http://gakuseisean.deviantart.com
+License: Free for personal desktop use only.
+http://www.iconarchive.com/show/radium-icons-by-gakuseisean/Key-icon.html
+
+shows.png
+Author: DBGthekafu - "Black-white 2 Vista" icon theme
+License: Reuse of the file allowed under the GPL
+http://commons.wikimedia.org/wiki/File:Tv_bw.png
+
+playlists.png
+Author: IconShock
+License: Free for commercial use
+http://www.iconarchive.com/show/cinema-icons-by-iconshock/directors-chair-icon.html
+
+settings.png
+Author: IconLab
+License: Free for non-commercial use
+http://www.iconeasy.com/icon/settings-1-icon/
+
+user.png
+Author: Aha-Soft
+License: Free for non-commercial use
+http://www.iconeasy.com/icon/user-4-icon/
+
+contacts.png
+Author: Aha-Soft
+License: Free for non-commercial use
+http://www.iconeasy.com/icon/user-group-icon/
+		
+most.png
+Author: IconLab
+License: Free for non-commercial use
+http://www.iconeasy.com/icon/settings-1-icon/
+
+favorites.png
+Modified version of DryIcons Heart icon
+Author: DryIcon
+License: Free
+http://dryicons.com/free-icons/preview/valentines-icons-set/
+
+newsubscriptions.png
+Author: unknown
+License: unknown but apparently free
+http://www.bestfreeicons.com/smimages/stars-icon.png
+
+previous.png
+Icon taken from nuka's original YouTube plugin, assumed to be ok license wise 
+
+subscriptions.png
+Author: "Fast Icon" 
+Icons by: FastIcon.com: http://www.fasticon.com
+http://www.iconarchive.com/show/web-2-icons-by-fasticon/Favorite-icon.html
+License: "Free for personal desktop use only."
+
+featured.png
+Author: Simiographics (Salvador Lopez)
+License: "Freeware"
+http://www.iconarchive.com/show/mixed-icons-by-simiographics/Bullhorn-icon.html
+
+next.png
+author: kyo-tux (Aeon Icon pack)
+License: "Free for non-commercial use
+http://kyo-tux.deviantart.com
+
+recommended.png		
+Author: Kyo Tux - Crysigns icons
+License: Free for commercial use
+http://www.iconfinder.com/icondetails/47037/256/smiley_icon
+
+music.png		
+Author: Kyo Tux - Crysigns icons
+License: Free for commercial use
+http://www.iconfinder.com/icondetails/47031/256/music_icon
+
+like.png		
+Author: Kyo Tux - Crysigns icons
+License: Free for commercial use
+http://www.iconfinder.com/icondetails/47035/128/select_valid_icon
+
+top.png
+Author IconShock
+License: "Free"
+http://www.iconspedia.com/icon/faves-high-detail-15-55.html
+
+feeds.png
+Author: "Fast Icon" 
+Icons by: FastIcon.com: http://www.fasticon.com
+http://www.iconarchive.com/show/web-2-icons-by-fasticon/Favorite-icon.html
+License: "Freeware"
+
+playbyid.png
+Author: Iconsland
+License: Free for non-commercial use.
+http://www.iconspedia.com/icon/bulb-grey--622.html
+	
+search.png
+Author: Harwen
+License: Free for non-commercial use.
+http://www.iconarchive.com/show/simple-icons-by-harwen/Search-icon.html
+	
+uploads.png
+Author: IconShock
+License: Linkware
+http://www.iconarchive.com/show/general-media-icons-by-iconshock/webcam-icon.html
+
+trailers.png
+Author: Svengraph
+License: CC Attribution 3.0 Unported 
+http://www.softicons.com/free-icons/internet-icons/i-love-icons-by-svengraph/cinema-icon
+
+discoball.png
+Author: HenrikDK
+License: Free for non-commercial use.
+
+explore.png
+Author: IconsLand
+License: Free for non-commercial use.
+http://www.veryicon.com/icons/system/vista-elements/binoculars-search.html
Binary file xbmc/plugin.video.youtube/icon.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/resources/language/Bulgarian/strings.xml	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,218 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<strings>
+    <!-- Category strings -->
+    <string id="30001">Емисии от YouTube</string>
+    <string id="30002">Любимите ви</string>
+    <string id="30003">Абонаментите ви</string>
+    <string id="30004">Нови видеа от абонаментите</string>
+    <string id="30005">Видеа, които сте качили</string>
+    <string id="30006">Търси</string>
+    <string id="30007">Търсене за...</string>
+    <string id="30008">За гледане по-късно</string>
+    <string id="30009">Най-дискутираните</string>
+    <string id="30010">С най-много препратки</string>
+    <string id="30011">Най-гледаните</string>
+    <string id="30012">Добавени най-скоро</string>
+    <string id="30013">С най-много отговори</string>
+    <string id="30014">Recently Featured</string>
+    <string id="30015">Най-харесваните</string>
+    <string id="30016">Най-високо оценените</string>
+    <string id="30017">Моите плейлисти</string>
+    <string id="30018">Моите контакти</string>
+    <string id="30019">YouTube Предлага</string>
+    <string id="30020">Харесвания</string>
+    <string id="30021">Абонаменти</string>
+    <string id="30022">Качвания</string>
+    <string id="30023">Плейлисти</string>
+    <string id="30024">Добавяне на контакт...</string>
+    <string id="30025">Премахни контакт</string>
+    <string id="30026">Добави контакт</string>
+    <string id="30027">Вписване..</string>
+    <string id="30028">Променете настройките на добавката</string>
+    <string id="30029">Контакт</string>
+    <string id="30030">Обновяване на папката..</string>
+    <string id="30031">Вписахте се успешно</string>
+    <string id="30032">YouTube Трейлъри</string>
+    <string id="30033">Популярни</string>
+    <string id="30034">В кината</string>
+    <string id="30035">Най-новите</string>
+    <string id="30036">Предстоящи филми</string>
+    <string id="30037">YouTube Диско!</string>
+    <string id="30038">Top 50</string>
+    <string id="30039">Най-популярните артисти</string>
+    <string id="30040">Диско търсене...</string>
+    <string id="30041">YouTube Категории</string>
+    <string id="30042">YouTube Предавания</string>
+    <string id="30043">YouTube Филми</string>
+    <string id="30044">Explore YouTube</string>
+    <string id="30045">Свалени</string>
+    <string id="30046">Популярни игри</string>
+    <string id="30047">Най-новите игри</string>
+    <string id="30048">Скоро излизащи игри</string>
+    <string id="30049">Модерни за момента видеа</string>
+    <string id="30050">Най-споделяните</string>
+    <string id="30051">YouTube На живо</string>
+    <string id="30052">YouTube Музика</string>
+    <string id="30053">Препоръчана музика</string>
+    <string id="30054">Популярни артисти</string>
+    <string id="30055">YouTube Top 100 музикална класация</string>
+    <string id="30056">Видеа, които сте харесали</string>
+    <string id="30057">YouTube EDU (Образование)</string>
+    <string id="30058">Сезон %s</string>
+    <string id="30059">Моята история</string>
+    
+    <!-- Plugin settings strings -->
+    <string id="30200">Потребител</string>
+    <string id="30201">Парола</string>
+    <string id="30203">За всички времена</string>
+    <string id="30204">Днес</string>
+    <string id="30205">Тази седмица</string>
+    <string id="30206">Този месец</string>
+    <string id="30207">Място за сваляне</string>
+    <string id="30208">Макс качество на видеото</string>
+    <string id="30209">Включи безопасния режим на YouTube</string>
+    <string id="30210">Видеа на страница</string>
+    <string id="30211">Запазвай последните (търсения)</string>
+    <string id="30212">Времетраене на известията (сек)</string>
+    <string id="30215">само SD</string>
+    <string id="30216">720p</string>
+    <string id="30217">1080p</string>
+    <string id="30218">General</string>
+    <string id="30219">Умерен</string>
+    <string id="30220">Стриктен</string>
+    <string id="30221">Показвай YouTube емисии само от следния район</string>
+    <string id="30222">Всички</string>
+	<string id="30223">Австралия</string>
+	<string id="30224">Бразилия</string>
+	<string id="30225">Канада</string>
+	<string id="30226">Чехия</string>
+	<string id="30227">Франция</string>
+	<string id="30228">Германия</string>
+	<string id="30229">Великобритания</string>
+	<string id="30230">Холандия</string>
+	<string id="30231">Хонконг</string>
+	<string id="30232">Индия</string>
+	<string id="30233">Ирландия</string>
+	<string id="30234">Израел</string>
+	<string id="30235">Италия</string>
+	<string id="30236">Япония</string>
+	<string id="30237">Мексико</string>
+	<string id="30238">Нова Зеландия</string>
+	<string id="30239">Полша</string>
+	<string id="30240">Русия</string>
+	<string id="30241">Южна Кореа</string>
+	<string id="30242">Испания</string>
+	<string id="30243">Швеция</string>
+	<string id="30244">Тайван</string>
+	<string id="30245">Съединени американски щати</string>    
+    <string id="30246">Дебъг</string>
+    <string id="30247">Папка</string>
+    <string id="30248">Показвай Explore YouTube</string>    
+    <string id="30249">Показвай Предложения от Youtube</string>
+    <string id="30250">Показвай За гледане по-късно</string>
+    <string id="30251">Показвай Харесвания</string>
+    <string id="30252">Показвай Контакти</string>
+    <string id="30253">Показвай Любими</string>
+    <string id="30254">Показвай Плейлисти</string>
+    <string id="30255">Показвай Абонаменти</string>
+    <string id="30256">Показвай Качените видеа</string>
+    <string id="30257">Показвай Свалени</string>
+    <string id="30258">Показвай Търсения</string>
+    <string id="30259">Вид в Confluence</string>
+    <string id="30260">Допълнителни</string>
+    <string id="30261">Макс качество на видеото (при сваляне)</string>
+    <string id="30262">= на гледаното видео</string>
+    <string id="30263">Показвай Youtube Диско!</string>
+    <string id="30264">Показвай Youtube Трейлъри</string>
+    <string id="30265">Времетраене (сек)</string>
+    <string id="30266">Показвай Browse YouTube</string>
+    <string id="30267">Показвай YouTube Предавания</string>
+    <string id="30268">Показвай YouTube Филми</string>
+    <string id="30269">Показвай емисиите</string>
+    <string id="30270">Explore YouTube</string>
+    <string id="30271">Видео миниатюри</string>
+    <string id="30272">Миниатюри навсякъде</string>
+    <string id="30273">Стандартния за XBMC</string>
+    <string id="30274">Транскрибирай аудиото</string>
+    <string id="30276">Език на субтитрите</string>
+    <string id="30277">Изкл</string>
+    <string id="30278">Английски</string>
+    <string id="30279">Испанки</string>
+    <string id="30280">Немски</string>
+    <string id="30281">Френски</string>
+    <string id="30282">Италиански</string>
+    <string id="30283">Японкси</string>
+    <string id="30284">Включи поясненията</string>
+    <string id="30285">Ползвай кеш сървърите на YouTube</string>
+    <string id="30286">Показвай YouTube Музика</string>
+    <string id="30287">Показвай YouTube На живо</string>
+
+    <!-- Menu strings -->
+    <string id="30500">Clear refinements</string>
+    <string id="30501">Свали видеото</string>
+    <string id="30502">Информация за видеото</string>
+    <string id="30503">Добави към любимите ми</string>
+    <string id="30504">Сложи на опашката</string>
+    <string id="30505">Refine to user</string>
+    <string id="30506">Изтрий от любимите ми</string>
+    <string id="30507">Сходни артисти</string>
+    <string id="30508">Изтрий търсенето</string>
+    <string id="30509">Още резултати</string>
+    <string id="30510">Покажи любимите</string>
+    <string id="30511">Покажи качените</string>
+    <string id="30512">Абониране за %s</string>
+    <string id="30513">Отписване от %s</string>
+    <string id="30514">Намери сходни</string>
+    <string id="30515">Редактирай търсенето</string>
+    <string id="30516">Още видеа от %s</string>
+    <string id="30517">Търси по автор</string>
+    <string id="30518">Изберете качество за видеото</string>
+    <string id="30519">Въведете име на контакта</string>
+    <string id="30520">Възпроизведи всички</string>
+    <string id="30521">Възпроизведи от тук</string>
+    <string id="30522">Разбъркай и възпроизведи всички</string>
+    <string id="30523">Сега се възпроизвежда...</string>        
+    <string id="30524">Редактирай диско търсенето</string>
+    <string id="30525">Изтрий диско търсенето</string>
+    <string id="30526">Покажи плейлистите</string>
+    <string id="30527">Свързани видеа</string>
+    <string id="30528">Добави в плейлист.</string>
+    <string id="30529">Нов плейлист..</string>
+    <string id="30530">Премахни от плейлиста</string>
+    <string id="30531">Преобърни плейлиста</string>
+    <string id="30539">Изтрий плейлиста</string>
+    <string id="30540">Изтрий артиста</string>
+	
+    <!-- messages -->
+    <string id="30600">Грешка</string>
+    <string id="30601">Няма резултати!</string>
+    <string id="30602">Няма скорошна активност</string>
+    <string id="30603">Възпроизвеждането се провали</string>
+    <string id="30604">Свалянето завърши</string>
+    <string id="30605">Подготовка за сваляне</string> 
+    <string id="30606">Потвърдете възрастта си браузър</string>
+    <string id="30607">Пропускане на сваляне</string>
+    <string id="30608">Няма видео с такъв идентификатор в YouTube!</string>    
+    <string id="30609">Вписването се провали</string>
+    <string id="30610">Refinement cleared</string>
+    <string id="30611">Не сте задали папка за сваляне</string>
+    <string id="30612">Не е наличен поток На Живо!</string>
+    <string id="30613">Добавен е контакт</string>
+    <string id="30614">Премахнат е контакт</string>
+    <string id="30615">Плейлиста е празен</string>
+    <string id="30616">Refinement added</string>
+    <string id="30617">Непозната грешка</string>
+    <string id="30618">Не може да бъде засечен url адреса на видеото</string>
+    <string id="30619">Не се поддържат RTMPe сваляния</string> 
+    <string id="30620">Типът на потока не се поддържа</string>
+    <string id="30621">Грешно потребителско име или парола</string>
+    <string id="30622">Възпроизвеждането изисква валиден YouTube профил</string>
+    <string id="30623">Достигнахте макс. брой опити за вписване.</string>
+    <string id="30624">Сваляне</string>
+    <string id="30625">Свалянето се провали</string>
+    <string id="30626">За момента не се поддържат RTMPe потоци</string> 
+    <string id="30627">Моля, въведете кода за потвърждаване в две стъпки</string> 
+    <string id="30628">Моля, въведете паролата си</string>
+    <string id="30629">SSL протоколът не се поддържа.</string>
+
+</strings>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/resources/language/English/strings.xml	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,223 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<strings>
+    <!-- Category strings -->
+    <string id="30001">YouTube Feeds</string>
+    <string id="30002">My Favorites</string>
+    <string id="30003">My Subscriptions</string>
+    <string id="30004">New Subscription Videos</string>
+    <string id="30005">My Uploads</string>
+    <string id="30006">Search</string>
+    <string id="30007">Search...</string>
+    <string id="30008">My Watch Later</string>
+    <string id="30009">Most Discussed</string>
+    <string id="30010">Most Linked</string>
+    <string id="30011">Most Viewed</string>
+    <string id="30012">Most Recent</string>
+    <string id="30013">Most Responded</string>
+    <string id="30014">Recently Featured</string>
+    <string id="30015">Top Favorites</string>
+    <string id="30016">Top Rated</string>
+    <string id="30017">My Playlists</string>
+    <string id="30018">My Contacts</string>
+    <string id="30019">YouTube Suggests</string>
+    <string id="30020">Favorites</string>
+    <string id="30021">Subscriptions</string>
+    <string id="30022">Uploads</string>
+    <string id="30023">Playlists</string>
+    <string id="30024">Add Contact...</string>
+    <string id="30025">Remove Contact</string>
+    <string id="30026">Add Contact</string>
+    <string id="30027">Login..</string>
+    <string id="30028">Change plugin settings</string>
+    <string id="30029">Contact</string>
+    <string id="30030">Refreshing folder..</string>
+    <string id="30031">Login success</string>
+    <string id="30032">YouTube Top 100 Trailers</string>
+    <string id="30033">Popular</string>
+    <string id="30034">In Theaters</string>
+    <string id="30035">Latest</string>
+    <string id="30036">Opening Soon</string>
+    <string id="30037">YouTube Disco!</string>
+    <string id="30038">Top 50</string>
+    <string id="30039">Most popular artists</string>
+    <string id="30040">Disco search...</string>
+    <string id="30041">YouTube Categories</string>
+    <string id="30042">YouTube Shows</string>
+    <string id="30043">YouTube Movies</string>
+    <string id="30044">Explore YouTube</string>
+    <string id="30045">My Downloads</string>
+    <string id="30046">Popular Games</string>
+    <string id="30047">Latest Games</string>
+    <string id="30048">Games Coming Soon</string>
+    <string id="30049">Trending Videos</string>
+    <string id="30050">Most Shared</string>
+    <string id="30051">YouTube Live</string>
+    <string id="30052">YouTube Music</string>
+    <string id="30053">Recommended Music</string>
+    <string id="30054">Popular Artists</string>
+    <string id="30055">YouTube Top 100 Music Chart</string>
+    <string id="30056">My Liked Videos</string>
+    <string id="30057">YouTube Education</string>
+    <string id="30058">Season %s</string>
+    <string id="30059">My History</string>
+
+    
+    <!-- Plugin settings strings -->
+    <string id="30200">Username</string>
+    <string id="30201">Password</string>
+    <string id="30203">All time</string>
+    <string id="30204">Today</string>
+    <string id="30205">This week</string>
+    <string id="30206">This month</string>
+    <string id="30207">Download Location</string>
+    <string id="30208">Max video quality</string>
+    <string id="30209">Enable YouTube SafeSearch</string>
+    <string id="30210">Videos per page</string>
+    <string id="30211">Searches to save</string>
+    <string id="30212">Notification length in seconds</string>
+    <string id="30213">Ask</string>
+    <string id="30215">SD only</string>
+    <string id="30216">720p</string>
+    <string id="30217">1080p</string>
+    <string id="30218">General</string>
+    <string id="30219">Moderate</string>
+    <string id="30220">Strict</string>
+    <string id="30221">YouTube feeds country region</string>
+    <string id="30222">All</string>
+	<string id="30223">Australia</string>
+	<string id="30224">Brazil</string>
+	<string id="30225">Canada</string>
+	<string id="30226">Czech Republic</string>
+	<string id="30227">France</string>
+	<string id="30228">Germany</string>
+	<string id="30229">Great Britain</string>
+	<string id="30230">Holland</string>
+	<string id="30231">Hong Kong</string>
+	<string id="30232">India</string>
+	<string id="30233">Ireland</string>
+	<string id="30234">Israel</string>
+	<string id="30235">Italy</string>
+	<string id="30236">Japan</string>
+	<string id="30237">Mexico</string>
+	<string id="30238">New Zealand</string>
+	<string id="30239">Poland</string>
+	<string id="30240">Russia</string>
+	<string id="30241">South Korea</string>
+	<string id="30242">Spain</string>
+	<string id="30243">Sweden</string>
+	<string id="30244">Taiwan</string>
+	<string id="30245">United States</string>    
+    <string id="30246">Debug</string>
+    <string id="30247">Folder</string>
+    <string id="30248">Show Explore YouTube</string>    
+    <string id="30249">Show Youtube Suggests</string>
+    <string id="30250">Show Watch Later</string>
+    <string id="30251">Show Liked Videos</string>
+    <string id="30252">Show Contacts</string>
+    <string id="30253">Show Favorites</string>
+    <string id="30254">Show Playlists</string>
+    <string id="30255">Show Subscriptions</string>
+    <string id="30256">Show Uploads</string>
+    <string id="30257">Show Downloads</string>
+    <string id="30258">Show Search</string>
+    <string id="30259">View mode in Confluence</string>
+    <string id="30260">Advanced</string>
+    <string id="30261">Max video quality (Downloading)</string>
+    <string id="30262">Same as playback</string>
+    <string id="30263">Show Youtube Disco!</string>
+    <string id="30264">Show Youtube Trailers</string>
+    <string id="30265">Timeout (in seconds)</string>
+    <string id="30266">Show Browse YouTube</string>
+    <string id="30267">Show YouTube Shows</string>
+    <string id="30268">Show YouTube Movies</string>
+    <string id="30269">Show Feeds</string>
+    <string id="30270">Explore YouTube</string>
+    <string id="30271">Video Thumbnails</string>
+    <string id="30272">Thumbnails Everywhere</string>
+    <string id="30273">XBMC Default</string>
+    <string id="30274">Transcribe Audio</string>
+    <string id="30276">Subtitle Language</string>
+    <string id="30277">Off</string>
+    <string id="30278">English</string>
+    <string id="30279">Spanish</string>
+    <string id="30280">German</string>
+    <string id="30281">French</string>
+    <string id="30282">Italian</string>
+    <string id="30283">Japanese</string>
+    <string id="30284">Enable annotations</string>
+    <string id="30285">Use YouTube cache servers</string>
+    <string id="30286">Show YouTube Music</string>
+    <string id="30287">Show YouTube Live</string>
+    <string id="30288">Show YouTube History</string>
+
+    <!-- Menu strings -->
+    <string id="30500">Clear refinements</string>
+    <string id="30501">Download video</string>
+    <string id="30502">Video information</string>
+    <string id="30503">Add to My Favorites</string>
+    <string id="30504">Queue video</string>
+    <string id="30505">Refine to user</string>
+    <string id="30506">Delete from My Favorites</string>
+    <string id="30507">Similar artists</string>
+    <string id="30508">Delete search</string>
+    <string id="30509">More results</string>
+    <string id="30510">View favorites</string>
+    <string id="30511">View uploads</string>
+    <string id="30512">Subscribe to %s</string>
+    <string id="30513">Unsubscribe from %s</string>
+    <string id="30514">Find similar</string>
+    <string id="30515">Edit search</string>
+    <string id="30516">More videos by %s</string>
+    <string id="30517">Search on author</string>
+    <string id="30518">Select video quality</string>
+    <string id="30519">Enter contact name</string>
+    <string id="30520">Play All</string>
+    <string id="30521">Play from here</string>
+    <string id="30522">Shuffle and Play All</string>
+    <string id="30523">Now Playing...</string>        
+    <string id="30524">Edit disco search</string>
+    <string id="30525">Delete disco search</string>
+    <string id="30526">View playlists</string>
+    <string id="30527">Related videos</string>
+    <string id="30528">Add to Playlist..</string>
+    <string id="30529">New Playlist..</string>
+    <string id="30530">Remove from Playlist</string>
+    <string id="30531">Reverse Playlist</string>
+    <string id="30539">Delete Playlist</string>
+    <string id="30540">Delete artist</string>
+	
+    <!-- messages -->
+    <string id="30600">Error</string>
+    <string id="30601">No results!</string>
+    <string id="30602">No recent activity</string>
+    <string id="30603">Playback failed</string>
+    <string id="30604">Download complete</string>
+    <string id="30605">Preparing download</string> 
+    <string id="30606">Verify your age in a browser</string>
+    <string id="30607">Skipping download</string>
+    <string id="30608">YouTube doesn't seem to know this video id!</string>    
+    <string id="30609">Login Failed</string>
+    <string id="30610">Refinement cleared</string>
+    <string id="30611">Download folder not set</string>
+    <string id="30612">Live steam not available!</string>
+    <string id="30613">Added contact</string>
+    <string id="30614">Removed contact</string>
+    <string id="30615">Playlist empty</string>
+    <string id="30616">Refinement added</string>
+    <string id="30617">Unknown error</string>
+    <string id="30618">Couldn't locate video url</string>
+    <string id="30619">RTMPe download not supported</string> 
+    <string id="30620">Stream type not supported</string>
+    <string id="30621">Bad username or password</string>
+    <string id="30622">Playback requires valid YouTube account</string>
+    <string id="30623">Maximum login attempts tried.</string>
+    <string id="30624">Downloading</string>
+    <string id="30625">Download Failed</string>
+    <string id="30626">RTMPe playback currently not supported</string> 
+    <string id="30627">Please provide 2-factor authentication PIN</string> 
+    <string id="30628">Please provide your password</string> 
+    <string id="30629">SSL protocol not supported.</string>
+
+    <!-- Dorfelite -->
+    <string id="31337">Dorfelite</string>
+</strings>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/resources/language/Russian/strings.xml	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<strings>
+    <!-- Category strings -->
+    <string id="30001">Каналы YouTube</string>
+    <string id="30002">Избранные</string>
+    <string id="30003">Подписки</string>
+    <string id="30004">Новые в подписках</string>
+    <string id="30005">Опубликованные</string>
+    <string id="30006">Поиск</string>
+    <string id="30007">Поиск...</string>
+    <string id="30008">Видео по ID</string>
+    <string id="30009">Макс. комментариев</string>
+    <string id="30010">Макс. ссылок</string>
+    <string id="30011">Макс. просмотров</string>
+    <string id="30012">Самое свежее</string>
+    <string id="30013">Макс. ответов</string>
+    <string id="30014">Рекомендуемое</string>
+    <string id="30015">Макс. в избранное</string>
+    <string id="30016">Макс. оценки</string>
+    <string id="30017">Плейлисты</string>
+    <string id="30018">Контакты</string>
+    <string id="30019">YouTube предлагает</string>
+    <string id="30020">Избранные</string>
+    <string id="30021">Подписки</string>
+    <string id="30022">Опубликованные</string>
+    <string id="30023">Плейлисты</string>
+    <string id="30024">Добавить контакт...</string>
+    <string id="30025">Удалить контакт</string>
+    <string id="30026">Добавить контакт</string>
+    <string id="30027">Вход...</string>
+    <string id="30028">Настройки</string>
+    <string id="30029">Контакт</string>
+    <string id="30030">Загрузка...</string>
+    <string id="30031">Вход ОК</string>
+    <string id="30032">Трейлеры</string>
+    <string id="30033">Популярные</string>
+    <string id="30034">В кинотеатрах</string>
+    <string id="30035">Последние</string>
+    <string id="30036">Скоро</string>
+    <string id="30037">YouTube Disco!</string>
+    <string id="30038">Лучшие 25</string>
+    <string id="30039">Популярные артисты</string>
+    <string id="30040">Поиск Disco...</string>
+    <string id="30041">Категории YouTube</string>
+    <string id="30042">Показывает YouTube</string>
+    <string id="30043">Фильмы YouTube</string>
+    <string id="30044">YouTube представляет</string>
+    <string id="30045">Закачки</string>
+    
+    <!-- Plugin settings strings -->
+    <string id="30200">Имя</string>
+    <string id="30201">Пароль</string>
+    <string id="30202">Период</string>
+    <string id="30203">Все</string>
+    <string id="30204">Сегодня</string>
+    <string id="30205">Неделя</string>
+    <string id="30206">Месяц</string>
+    <string id="30207">Папка закачек</string>
+    <string id="30208">Макс. качество видео</string>
+    <string id="30209">Безопасный поиск</string>
+    <string id="30210">Видео на странице</string>
+    <string id="30211">Сохраненных поисков</string>
+    <string id="30212">Вид папки</string>
+    <string id="30213">Вид видео</string>
+    <string id="30214">Уведомления (сек.)</string>
+    <string id="30215">1080p</string>
+    <string id="30216">720p</string>
+    <string id="30217">SD only</string>
+    <string id="30218">Выкл.</string>
+    <string id="30219">Умеренно</string>
+    <string id="30220">Строго</string>
+    <string id="30221">Регион</string>
+    <string id="30222">Все</string>
+	<string id="30223">Australia</string>
+	<string id="30224">Brazil</string>
+	<string id="30225">Canada</string>
+	<string id="30226">Czech Republic</string>
+	<string id="30227">France</string>
+	<string id="30228">Germany</string>
+	<string id="30229">Great Britain</string>
+	<string id="30230">The Netherlands</string>
+	<string id="30231">Hong Kong</string>
+	<string id="30232">India</string>
+	<string id="30233">Ireland</string>
+	<string id="30234">Israel</string>
+	<string id="30235">Italy</string>
+	<string id="30236">Japan</string>
+	<string id="30237">Mexico</string>
+	<string id="30238">New Zealand</string>
+	<string id="30239">Poland</string>
+	<string id="30240">Россия</string>
+	<string id="30241">South Korea</string>
+	<string id="30242">Spain</string>
+	<string id="30243">Sweden</string>
+	<string id="30244">Taiwan</string>
+	<string id="30245">United States</string>    
+    <string id="30246">Debug</string>
+    <string id="30247">Основные</string>
+    <string id="30248">Папка</string>
+    <string id="30249">Показывать Каналы</string>
+    <string id="30250">Показывать YT предлагает</string>
+    <string id="30251">Показывать Контакты</string>
+    <string id="30252">Показывать Избранное</string>
+    <string id="30253">Показывать Плейлисты</string>
+    <string id="30254">Показывать Подписки</string>
+    <string id="30255">Показывать Опубликованное</string>
+    <string id="30256">Показывать Поиск</string>
+    <string id="30257">Показывать Видео по ID</string>
+    <string id="30258">Вид в Confluence</string>
+    <string id="30259">Еще</string>
+    <string id="30260">Качество загрузки</string>
+    <string id="30261">как и просмотр</string>
+    <string id="30262">Выбор качества</string> 
+    <string id="30263">Показывать Youtube Disco!</string>
+    <string id="30264">Показывать Трейлеры</string>
+    <string id="30265">Задержка (сек.)</string>
+    <string id="30266">Показывать Browse YouTube</string>
+    <string id="30267">Показывать Показывает YT</string>
+    <string id="30268">Показывать Фильмы</string>
+    <string id="30269">Показывать YT представляет</string>
+    <string id="30270">YouTube представляет</string>
+    <string id="30271">Эскизы видео</string>
+    <string id="30272">Эскизы везде</string>
+    <string id="30273">По умолчанию XBMC</string>
+	<string id="30274">Показывать Загрузки</string>
+	
+    <!-- Menu strings -->
+    <string id="30500">Очистить уточнения</string>
+    <string id="30501">Скачать видео</string>
+    <string id="30502">Свойства видео</string>
+    <string id="30503">В Избранное</string>
+    <string id="30504">В Плейлист</string>
+    <string id="30505">Refine to user</string>
+    <string id="30506">Убрать из Избранного</string>
+    <string id="30507">Папку в плейлист</string>
+    <string id="30508">Удалить поиск</string>
+    <string id="30509">Еще</string>
+    <string id="30510">Изменение подписки в избранное</string>
+    <string id="30511">Изменение подписки в опубликованное</string>
+    <string id="30512">Подписаться на %s</string>
+    <string id="30513">Отменить подписку на %s</string>
+    <string id="30514">Найти похожие</string>
+    <string id="30515">Изменить поиск</string>
+    <string id="30516">Еще видео от %s</string>
+    <string id="30517">Искать по автору</string>
+    <string id="30518">Видео ID</string>
+    <string id="30519">Ввведите имя контакта</string>
+    <string id="30520">Воспроизвести в 1080p</string>
+    <string id="30521">Воспроизвести в 720p</string>
+    <string id="30522">Воспроизвести в SD</string>
+    <string id="30523">%s Disco!</string>
+    <string id="30524">Изменить поиск Disco</string>
+    <string id="30525">Удалить поиск Disco</string>
+    <string id="30526">Воспроизвести папку</string>
+    <string id="30527">Сейчас играет...</string>
+    <string id="30528">Изменение подписки на плейлисты</string>
+    <string id="30529">Похожие видео</string>
+
+    <!-- messages -->
+    <string id="30600">Ошибка</string>
+    <string id="30601">Нет результатов!</string>
+    <string id="30602">Нет новых</string>
+    <string id="30603">Воспроизведение не удалось</string>
+    <string id="30604">Загрузка завершена</string>
+    <string id="30605">YouTube не отвечает</string>
+    <string id="30606">Неизвестная ошибка:</string>
+    <string id="30607">Нет подходящего разрешения</string>
+    <string id="30608">Только для совершеннолетних!</string>
+    <string id="30609">Ошибка входа</string>
+    <string id="30610">Уточнение очищено</string>
+    <string id="30611">Папка для загрузок не задана</string>
+    <string id="30612">Начинаю закачку</string>
+    <string id="30613">Контакт добавлен</string>
+    <string id="30614">Контакт удален</string>
+    <string id="30615">Не найдено</string>
+    <string id="30616">Уточнение </string>
+    <string id="30617">Неизвестная ошибка</string>
+    <string id="30618">Не удается извлечь адрес видео</string>
+    <string id="30619">Не удалось очистить YouTube</string>
+    <string id="30620">Формат не поддерживается</string>
+    <string id="30621">Неправильное имя или пароль</string>
+    <string id="30622">Воспроизведение не удалось, попробуйте войти в аккаунт</string>
+    <string id="30623">Достигнуто максимальное колличество попыток входа.</string>
+</strings>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/resources/language/language/Bulgarian/strings.xml	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,218 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<strings>
+    <!-- Category strings -->
+    <string id="30001">Емисии от YouTube</string>
+    <string id="30002">Любимите ви</string>
+    <string id="30003">Абонаментите ви</string>
+    <string id="30004">Нови видеа от абонаментите</string>
+    <string id="30005">Видеа, които сте качили</string>
+    <string id="30006">Търси</string>
+    <string id="30007">Търсене за...</string>
+    <string id="30008">За гледане по-късно</string>
+    <string id="30009">Най-дискутираните</string>
+    <string id="30010">С най-много препратки</string>
+    <string id="30011">Най-гледаните</string>
+    <string id="30012">Добавени най-скоро</string>
+    <string id="30013">С най-много отговори</string>
+    <string id="30014">Recently Featured</string>
+    <string id="30015">Най-харесваните</string>
+    <string id="30016">Най-високо оценените</string>
+    <string id="30017">Моите плейлисти</string>
+    <string id="30018">Моите контакти</string>
+    <string id="30019">YouTube Предлага</string>
+    <string id="30020">Харесвания</string>
+    <string id="30021">Абонаменти</string>
+    <string id="30022">Качвания</string>
+    <string id="30023">Плейлисти</string>
+    <string id="30024">Добавяне на контакт...</string>
+    <string id="30025">Премахни контакт</string>
+    <string id="30026">Добави контакт</string>
+    <string id="30027">Вписване..</string>
+    <string id="30028">Променете настройките на добавката</string>
+    <string id="30029">Контакт</string>
+    <string id="30030">Обновяване на папката..</string>
+    <string id="30031">Вписахте се успешно</string>
+    <string id="30032">YouTube Трейлъри</string>
+    <string id="30033">Популярни</string>
+    <string id="30034">В кината</string>
+    <string id="30035">Най-новите</string>
+    <string id="30036">Предстоящи филми</string>
+    <string id="30037">YouTube Диско!</string>
+    <string id="30038">Top 50</string>
+    <string id="30039">Най-популярните артисти</string>
+    <string id="30040">Диско търсене...</string>
+    <string id="30041">YouTube Категории</string>
+    <string id="30042">YouTube Предавания</string>
+    <string id="30043">YouTube Филми</string>
+    <string id="30044">Explore YouTube</string>
+    <string id="30045">Свалени</string>
+    <string id="30046">Популярни игри</string>
+    <string id="30047">Най-новите игри</string>
+    <string id="30048">Скоро излизащи игри</string>
+    <string id="30049">Модерни за момента видеа</string>
+    <string id="30050">Най-споделяните</string>
+    <string id="30051">YouTube На живо</string>
+    <string id="30052">YouTube Музика</string>
+    <string id="30053">Препоръчана музика</string>
+    <string id="30054">Популярни артисти</string>
+    <string id="30055">YouTube Top 100 музикална класация</string>
+    <string id="30056">Видеа, които сте харесали</string>
+    <string id="30057">YouTube EDU (Образование)</string>
+    <string id="30058">Сезон %s</string>
+    <string id="30059">Моята история</string>
+    
+    <!-- Plugin settings strings -->
+    <string id="30200">Потребител</string>
+    <string id="30201">Парола</string>
+    <string id="30203">За всички времена</string>
+    <string id="30204">Днес</string>
+    <string id="30205">Тази седмица</string>
+    <string id="30206">Този месец</string>
+    <string id="30207">Място за сваляне</string>
+    <string id="30208">Макс качество на видеото</string>
+    <string id="30209">Включи безопасния режим на YouTube</string>
+    <string id="30210">Видеа на страница</string>
+    <string id="30211">Запазвай последните (търсения)</string>
+    <string id="30212">Времетраене на известията (сек)</string>
+    <string id="30215">само SD</string>
+    <string id="30216">720p</string>
+    <string id="30217">1080p</string>
+    <string id="30218">General</string>
+    <string id="30219">Умерен</string>
+    <string id="30220">Стриктен</string>
+    <string id="30221">Показвай YouTube емисии само от следния район</string>
+    <string id="30222">Всички</string>
+	<string id="30223">Австралия</string>
+	<string id="30224">Бразилия</string>
+	<string id="30225">Канада</string>
+	<string id="30226">Чехия</string>
+	<string id="30227">Франция</string>
+	<string id="30228">Германия</string>
+	<string id="30229">Великобритания</string>
+	<string id="30230">Холандия</string>
+	<string id="30231">Хонконг</string>
+	<string id="30232">Индия</string>
+	<string id="30233">Ирландия</string>
+	<string id="30234">Израел</string>
+	<string id="30235">Италия</string>
+	<string id="30236">Япония</string>
+	<string id="30237">Мексико</string>
+	<string id="30238">Нова Зеландия</string>
+	<string id="30239">Полша</string>
+	<string id="30240">Русия</string>
+	<string id="30241">Южна Кореа</string>
+	<string id="30242">Испания</string>
+	<string id="30243">Швеция</string>
+	<string id="30244">Тайван</string>
+	<string id="30245">Съединени американски щати</string>    
+    <string id="30246">Дебъг</string>
+    <string id="30247">Папка</string>
+    <string id="30248">Показвай Explore YouTube</string>    
+    <string id="30249">Показвай Предложения от Youtube</string>
+    <string id="30250">Показвай За гледане по-късно</string>
+    <string id="30251">Показвай Харесвания</string>
+    <string id="30252">Показвай Контакти</string>
+    <string id="30253">Показвай Любими</string>
+    <string id="30254">Показвай Плейлисти</string>
+    <string id="30255">Показвай Абонаменти</string>
+    <string id="30256">Показвай Качените видеа</string>
+    <string id="30257">Показвай Свалени</string>
+    <string id="30258">Показвай Търсения</string>
+    <string id="30259">Вид в Confluence</string>
+    <string id="30260">Допълнителни</string>
+    <string id="30261">Макс качество на видеото (при сваляне)</string>
+    <string id="30262">= на гледаното видео</string>
+    <string id="30263">Показвай Youtube Диско!</string>
+    <string id="30264">Показвай Youtube Трейлъри</string>
+    <string id="30265">Времетраене (сек)</string>
+    <string id="30266">Показвай Browse YouTube</string>
+    <string id="30267">Показвай YouTube Предавания</string>
+    <string id="30268">Показвай YouTube Филми</string>
+    <string id="30269">Показвай емисиите</string>
+    <string id="30270">Explore YouTube</string>
+    <string id="30271">Видео миниатюри</string>
+    <string id="30272">Миниатюри навсякъде</string>
+    <string id="30273">Стандартния за XBMC</string>
+    <string id="30274">Транскрибирай аудиото</string>
+    <string id="30276">Език на субтитрите</string>
+    <string id="30277">Изкл</string>
+    <string id="30278">Английски</string>
+    <string id="30279">Испанки</string>
+    <string id="30280">Немски</string>
+    <string id="30281">Френски</string>
+    <string id="30282">Италиански</string>
+    <string id="30283">Японкси</string>
+    <string id="30284">Включи поясненията</string>
+    <string id="30285">Ползвай кеш сървърите на YouTube</string>
+    <string id="30286">Показвай YouTube Музика</string>
+    <string id="30287">Показвай YouTube На живо</string>
+
+    <!-- Menu strings -->
+    <string id="30500">Clear refinements</string>
+    <string id="30501">Свали видеото</string>
+    <string id="30502">Информация за видеото</string>
+    <string id="30503">Добави към любимите ми</string>
+    <string id="30504">Сложи на опашката</string>
+    <string id="30505">Refine to user</string>
+    <string id="30506">Изтрий от любимите ми</string>
+    <string id="30507">Сходни артисти</string>
+    <string id="30508">Изтрий търсенето</string>
+    <string id="30509">Още резултати</string>
+    <string id="30510">Покажи любимите</string>
+    <string id="30511">Покажи качените</string>
+    <string id="30512">Абониране за %s</string>
+    <string id="30513">Отписване от %s</string>
+    <string id="30514">Намери сходни</string>
+    <string id="30515">Редактирай търсенето</string>
+    <string id="30516">Още видеа от %s</string>
+    <string id="30517">Търси по автор</string>
+    <string id="30518">Изберете качество за видеото</string>
+    <string id="30519">Въведете име на контакта</string>
+    <string id="30520">Възпроизведи всички</string>
+    <string id="30521">Възпроизведи от тук</string>
+    <string id="30522">Разбъркай и възпроизведи всички</string>
+    <string id="30523">Сега се възпроизвежда...</string>        
+    <string id="30524">Редактирай диско търсенето</string>
+    <string id="30525">Изтрий диско търсенето</string>
+    <string id="30526">Покажи плейлистите</string>
+    <string id="30527">Свързани видеа</string>
+    <string id="30528">Добави в плейлист.</string>
+    <string id="30529">Нов плейлист..</string>
+    <string id="30530">Премахни от плейлиста</string>
+    <string id="30531">Преобърни плейлиста</string>
+    <string id="30539">Изтрий плейлиста</string>
+    <string id="30540">Изтрий артиста</string>
+	
+    <!-- messages -->
+    <string id="30600">Грешка</string>
+    <string id="30601">Няма резултати!</string>
+    <string id="30602">Няма скорошна активност</string>
+    <string id="30603">Възпроизвеждането се провали</string>
+    <string id="30604">Свалянето завърши</string>
+    <string id="30605">Подготовка за сваляне</string> 
+    <string id="30606">Потвърдете възрастта си браузър</string>
+    <string id="30607">Пропускане на сваляне</string>
+    <string id="30608">Няма видео с такъв идентификатор в YouTube!</string>    
+    <string id="30609">Вписването се провали</string>
+    <string id="30610">Refinement cleared</string>
+    <string id="30611">Не сте задали папка за сваляне</string>
+    <string id="30612">Не е наличен поток На Живо!</string>
+    <string id="30613">Добавен е контакт</string>
+    <string id="30614">Премахнат е контакт</string>
+    <string id="30615">Плейлиста е празен</string>
+    <string id="30616">Refinement added</string>
+    <string id="30617">Непозната грешка</string>
+    <string id="30618">Не може да бъде засечен url адреса на видеото</string>
+    <string id="30619">Не се поддържат RTMPe сваляния</string> 
+    <string id="30620">Типът на потока не се поддържа</string>
+    <string id="30621">Грешно потребителско име или парола</string>
+    <string id="30622">Възпроизвеждането изисква валиден YouTube профил</string>
+    <string id="30623">Достигнахте макс. брой опити за вписване.</string>
+    <string id="30624">Сваляне</string>
+    <string id="30625">Свалянето се провали</string>
+    <string id="30626">За момента не се поддържат RTMPe потоци</string> 
+    <string id="30627">Моля, въведете кода за потвърждаване в две стъпки</string> 
+    <string id="30628">Моля, въведете паролата си</string>
+    <string id="30629">SSL протоколът не се поддържа.</string>
+
+</strings>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/resources/language/language/English/strings.xml	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,222 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<strings>
+    <!-- Category strings -->
+    <string id="30001">YouTube Feeds</string>
+    <string id="30002">My Favorites</string>
+    <string id="30003">My Subscriptions</string>
+    <string id="30004">New Subscription Videos</string>
+    <string id="30005">My Uploads</string>
+    <string id="30006">Search</string>
+    <string id="30007">Search...</string>
+    <string id="30008">My Watch Later</string>
+    <string id="30009">Most Discussed</string>
+    <string id="30010">Most Linked</string>
+    <string id="30011">Most Viewed</string>
+    <string id="30012">Most Recent</string>
+    <string id="30013">Most Responded</string>
+    <string id="30014">Recently Featured</string>
+    <string id="30015">Top Favorites</string>
+    <string id="30016">Top Rated</string>
+    <string id="30017">My Playlists</string>
+    <string id="30018">My Contacts</string>
+    <string id="30019">YouTube Suggests</string>
+    <string id="30020">Favorites</string>
+    <string id="30021">Subscriptions</string>
+    <string id="30022">Uploads</string>
+    <string id="30023">Playlists</string>
+    <string id="30024">Add Contact...</string>
+    <string id="30025">Remove Contact</string>
+    <string id="30026">Add Contact</string>
+    <string id="30027">Login..</string>
+    <string id="30028">Change plugin settings</string>
+    <string id="30029">Contact</string>
+    <string id="30030">Refreshing folder..</string>
+    <string id="30031">Login success</string>
+    <string id="30032">YouTube Trailers</string>
+    <string id="30033">Popular</string>
+    <string id="30034">In Theaters</string>
+    <string id="30035">Latest</string>
+    <string id="30036">Opening Soon</string>
+    <string id="30037">YouTube Disco!</string>
+    <string id="30038">Top 50</string>
+    <string id="30039">Most popular artists</string>
+    <string id="30040">Disco search...</string>
+    <string id="30041">YouTube Categories</string>
+    <string id="30042">YouTube Shows</string>
+    <string id="30043">YouTube Movies</string>
+    <string id="30044">Explore YouTube</string>
+    <string id="30045">My Downloads</string>
+    <string id="30046">Popular Games</string>
+    <string id="30047">Latest Games</string>
+    <string id="30048">Games Coming Soon</string>
+    <string id="30049">Trending Videos</string>
+    <string id="30050">Most Shared</string>
+    <string id="30051">YouTube Live</string>
+    <string id="30052">YouTube Music</string>
+    <string id="30053">Recommended Music</string>
+    <string id="30054">Popular Artists</string>
+    <string id="30055">YouTube Top 100 Music Chart</string>
+    <string id="30056">My Liked Videos</string>
+    <string id="30057">YouTube Education</string>
+    <string id="30058">Season %s</string>
+    <string id="30059">My History</string>
+
+    
+    <!-- Plugin settings strings -->
+    <string id="30200">Username</string>
+    <string id="30201">Password</string>
+    <string id="30203">All time</string>
+    <string id="30204">Today</string>
+    <string id="30205">This week</string>
+    <string id="30206">This month</string>
+    <string id="30207">Download Location</string>
+    <string id="30208">Max video quality</string>
+    <string id="30209">Enable YouTube SafeSearch</string>
+    <string id="30210">Videos per page</string>
+    <string id="30211">Searches to save</string>
+    <string id="30212">Notification length in seconds</string>
+    <string id="30213">Ask</string>
+    <string id="30215">SD only</string>
+    <string id="30216">720p</string>
+    <string id="30217">1080p</string>
+    <string id="30218">General</string>
+    <string id="30219">Moderate</string>
+    <string id="30220">Strict</string>
+    <string id="30221">YouTube feeds country region</string>
+    <string id="30222">All</string>
+	<string id="30223">Australia</string>
+	<string id="30224">Brazil</string>
+	<string id="30225">Canada</string>
+	<string id="30226">Czech Republic</string>
+	<string id="30227">France</string>
+	<string id="30228">Germany</string>
+	<string id="30229">Great Britain</string>
+	<string id="30230">Holland</string>
+	<string id="30231">Hong Kong</string>
+	<string id="30232">India</string>
+	<string id="30233">Ireland</string>
+	<string id="30234">Israel</string>
+	<string id="30235">Italy</string>
+	<string id="30236">Japan</string>
+	<string id="30237">Mexico</string>
+	<string id="30238">New Zealand</string>
+	<string id="30239">Poland</string>
+	<string id="30240">Russia</string>
+	<string id="30241">South Korea</string>
+	<string id="30242">Spain</string>
+	<string id="30243">Sweden</string>
+	<string id="30244">Taiwan</string>
+	<string id="30245">United States</string>    
+    <string id="30246">Debug</string>
+    <string id="30247">Folder</string>
+    <string id="30248">Show Explore YouTube</string>    
+    <string id="30249">Show Youtube Suggests</string>
+    <string id="30250">Show Watch Later</string>
+    <string id="30251">Show Liked Videos</string>
+    <string id="30252">Show Contacts</string>
+    <string id="30253">Show Favorites</string>
+    <string id="30254">Show Playlists</string>
+    <string id="30255">Show Subscriptions</string>
+    <string id="30256">Show Uploads</string>
+    <string id="30257">Show Downloads</string>
+    <string id="30258">Show Search</string>
+    <string id="30259">View mode in Confluence</string>
+    <string id="30260">Advanced</string>
+    <string id="30261">Max video quality (Downloading)</string>
+    <string id="30262">Same as playback</string>
+    <string id="30263">Show Youtube Disco!</string>
+    <string id="30264">Show Youtube Trailers</string>
+    <string id="30265">Timeout (in seconds)</string>
+    <string id="30266">Show Browse YouTube</string>
+    <string id="30267">Show YouTube Shows</string>
+    <string id="30268">Show YouTube Movies</string>
+    <string id="30269">Show Feeds</string>
+    <string id="30270">Explore YouTube</string>
+    <string id="30271">Video Thumbnails</string>
+    <string id="30272">Thumbnails Everywhere</string>
+    <string id="30273">XBMC Default</string>
+    <string id="30274">Transcribe Audio</string>
+    <string id="30276">Subtitle Language</string>
+    <string id="30277">Off</string>
+    <string id="30278">English</string>
+    <string id="30279">Spanish</string>
+    <string id="30280">German</string>
+    <string id="30281">French</string>
+    <string id="30282">Italian</string>
+    <string id="30283">Japanese</string>
+    <string id="30284">Enable annotations</string>
+    <string id="30285">Use YouTube cache servers</string>
+    <string id="30286">Show YouTube Music</string>
+    <string id="30287">Show YouTube Live</string>
+
+    <!-- Menu strings -->
+    <string id="30500">Clear refinements</string>
+    <string id="30501">Download video</string>
+    <string id="30502">Video information</string>
+    <string id="30503">Add to My Favorites</string>
+    <string id="30504">Queue video</string>
+    <string id="30505">Refine to user</string>
+    <string id="30506">Delete from My Favorites</string>
+    <string id="30507">Similar artists</string>
+    <string id="30508">Delete search</string>
+    <string id="30509">More results</string>
+    <string id="30510">View favorites</string>
+    <string id="30511">View uploads</string>
+    <string id="30512">Subscribe to %s</string>
+    <string id="30513">Unsubscribe from %s</string>
+    <string id="30514">Find similar</string>
+    <string id="30515">Edit search</string>
+    <string id="30516">More videos by %s</string>
+    <string id="30517">Search on author</string>
+    <string id="30518">Select video quality</string>
+    <string id="30519">Enter contact name</string>
+    <string id="30520">Play All</string>
+    <string id="30521">Play from here</string>
+    <string id="30522">Shuffle and Play All</string>
+    <string id="30523">Now Playing...</string>        
+    <string id="30524">Edit disco search</string>
+    <string id="30525">Delete disco search</string>
+    <string id="30526">View playlists</string>
+    <string id="30527">Related videos</string>
+    <string id="30528">Add to Playlist..</string>
+    <string id="30529">New Playlist..</string>
+    <string id="30530">Remove from Playlist</string>
+    <string id="30531">Reverse Playlist</string>
+    <string id="30539">Delete Playlist</string>
+    <string id="30540">Delete artist</string>
+	
+    <!-- messages -->
+    <string id="30600">Error</string>
+    <string id="30601">No results!</string>
+    <string id="30602">No recent activity</string>
+    <string id="30603">Playback failed</string>
+    <string id="30604">Download complete</string>
+    <string id="30605">Preparing download</string> 
+    <string id="30606">Verify your age in a browser</string>
+    <string id="30607">Skipping download</string>
+    <string id="30608">YouTube doesn't seem to know this video id!</string>    
+    <string id="30609">Login Failed</string>
+    <string id="30610">Refinement cleared</string>
+    <string id="30611">Download folder not set</string>
+    <string id="30612">Live steam not available!</string>
+    <string id="30613">Added contact</string>
+    <string id="30614">Removed contact</string>
+    <string id="30615">Playlist empty</string>
+    <string id="30616">Refinement added</string>
+    <string id="30617">Unknown error</string>
+    <string id="30618">Couldn't locate video url</string>
+    <string id="30619">RTMPe download not supported</string> 
+    <string id="30620">Stream type not supported</string>
+    <string id="30621">Bad username or password</string>
+    <string id="30622">Playback requires valid YouTube account</string>
+    <string id="30623">Maximum login attempts tried.</string>
+    <string id="30624">Downloading</string>
+    <string id="30625">Download Failed</string>
+    <string id="30626">RTMPe playback currently not supported</string> 
+    <string id="30627">Please provide 2-factor authentication PIN</string> 
+    <string id="30628">Please provide your password</string> 
+    <string id="30629">SSL protocol not supported.</string>
+
+    <!-- Dorfelite -->
+    <string id="31337">Dorfelite</string>
+</strings>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/resources/language/language/Russian/strings.xml	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<strings>
+    <!-- Category strings -->
+    <string id="30001">Каналы YouTube</string>
+    <string id="30002">Избранные</string>
+    <string id="30003">Подписки</string>
+    <string id="30004">Новые в подписках</string>
+    <string id="30005">Опубликованные</string>
+    <string id="30006">Поиск</string>
+    <string id="30007">Поиск...</string>
+    <string id="30008">Видео по ID</string>
+    <string id="30009">Макс. комментариев</string>
+    <string id="30010">Макс. ссылок</string>
+    <string id="30011">Макс. просмотров</string>
+    <string id="30012">Самое свежее</string>
+    <string id="30013">Макс. ответов</string>
+    <string id="30014">Рекомендуемое</string>
+    <string id="30015">Макс. в избранное</string>
+    <string id="30016">Макс. оценки</string>
+    <string id="30017">Плейлисты</string>
+    <string id="30018">Контакты</string>
+    <string id="30019">YouTube предлагает</string>
+    <string id="30020">Избранные</string>
+    <string id="30021">Подписки</string>
+    <string id="30022">Опубликованные</string>
+    <string id="30023">Плейлисты</string>
+    <string id="30024">Добавить контакт...</string>
+    <string id="30025">Удалить контакт</string>
+    <string id="30026">Добавить контакт</string>
+    <string id="30027">Вход...</string>
+    <string id="30028">Настройки</string>
+    <string id="30029">Контакт</string>
+    <string id="30030">Загрузка...</string>
+    <string id="30031">Вход ОК</string>
+    <string id="30032">Трейлеры</string>
+    <string id="30033">Популярные</string>
+    <string id="30034">В кинотеатрах</string>
+    <string id="30035">Последние</string>
+    <string id="30036">Скоро</string>
+    <string id="30037">YouTube Disco!</string>
+    <string id="30038">Лучшие 25</string>
+    <string id="30039">Популярные артисты</string>
+    <string id="30040">Поиск Disco...</string>
+    <string id="30041">Категории YouTube</string>
+    <string id="30042">Показывает YouTube</string>
+    <string id="30043">Фильмы YouTube</string>
+    <string id="30044">YouTube представляет</string>
+    <string id="30045">Закачки</string>
+    
+    <!-- Plugin settings strings -->
+    <string id="30200">Имя</string>
+    <string id="30201">Пароль</string>
+    <string id="30202">Период</string>
+    <string id="30203">Все</string>
+    <string id="30204">Сегодня</string>
+    <string id="30205">Неделя</string>
+    <string id="30206">Месяц</string>
+    <string id="30207">Папка закачек</string>
+    <string id="30208">Макс. качество видео</string>
+    <string id="30209">Безопасный поиск</string>
+    <string id="30210">Видео на странице</string>
+    <string id="30211">Сохраненных поисков</string>
+    <string id="30212">Вид папки</string>
+    <string id="30213">Вид видео</string>
+    <string id="30214">Уведомления (сек.)</string>
+    <string id="30215">1080p</string>
+    <string id="30216">720p</string>
+    <string id="30217">SD only</string>
+    <string id="30218">Выкл.</string>
+    <string id="30219">Умеренно</string>
+    <string id="30220">Строго</string>
+    <string id="30221">Регион</string>
+    <string id="30222">Все</string>
+	<string id="30223">Australia</string>
+	<string id="30224">Brazil</string>
+	<string id="30225">Canada</string>
+	<string id="30226">Czech Republic</string>
+	<string id="30227">France</string>
+	<string id="30228">Germany</string>
+	<string id="30229">Great Britain</string>
+	<string id="30230">The Netherlands</string>
+	<string id="30231">Hong Kong</string>
+	<string id="30232">India</string>
+	<string id="30233">Ireland</string>
+	<string id="30234">Israel</string>
+	<string id="30235">Italy</string>
+	<string id="30236">Japan</string>
+	<string id="30237">Mexico</string>
+	<string id="30238">New Zealand</string>
+	<string id="30239">Poland</string>
+	<string id="30240">Россия</string>
+	<string id="30241">South Korea</string>
+	<string id="30242">Spain</string>
+	<string id="30243">Sweden</string>
+	<string id="30244">Taiwan</string>
+	<string id="30245">United States</string>    
+    <string id="30246">Debug</string>
+    <string id="30247">Основные</string>
+    <string id="30248">Папка</string>
+    <string id="30249">Показывать Каналы</string>
+    <string id="30250">Показывать YT предлагает</string>
+    <string id="30251">Показывать Контакты</string>
+    <string id="30252">Показывать Избранное</string>
+    <string id="30253">Показывать Плейлисты</string>
+    <string id="30254">Показывать Подписки</string>
+    <string id="30255">Показывать Опубликованное</string>
+    <string id="30256">Показывать Поиск</string>
+    <string id="30257">Показывать Видео по ID</string>
+    <string id="30258">Вид в Confluence</string>
+    <string id="30259">Еще</string>
+    <string id="30260">Качество загрузки</string>
+    <string id="30261">как и просмотр</string>
+    <string id="30262">Выбор качества</string> 
+    <string id="30263">Показывать Youtube Disco!</string>
+    <string id="30264">Показывать Трейлеры</string>
+    <string id="30265">Задержка (сек.)</string>
+    <string id="30266">Показывать Browse YouTube</string>
+    <string id="30267">Показывать Показывает YT</string>
+    <string id="30268">Показывать Фильмы</string>
+    <string id="30269">Показывать YT представляет</string>
+    <string id="30270">YouTube представляет</string>
+    <string id="30271">Эскизы видео</string>
+    <string id="30272">Эскизы везде</string>
+    <string id="30273">По умолчанию XBMC</string>
+	<string id="30274">Показывать Загрузки</string>
+	
+    <!-- Menu strings -->
+    <string id="30500">Очистить уточнения</string>
+    <string id="30501">Скачать видео</string>
+    <string id="30502">Свойства видео</string>
+    <string id="30503">В Избранное</string>
+    <string id="30504">В Плейлист</string>
+    <string id="30505">Refine to user</string>
+    <string id="30506">Убрать из Избранного</string>
+    <string id="30507">Папку в плейлист</string>
+    <string id="30508">Удалить поиск</string>
+    <string id="30509">Еще</string>
+    <string id="30510">Изменение подписки в избранное</string>
+    <string id="30511">Изменение подписки в опубликованное</string>
+    <string id="30512">Подписаться на %s</string>
+    <string id="30513">Отменить подписку на %s</string>
+    <string id="30514">Найти похожие</string>
+    <string id="30515">Изменить поиск</string>
+    <string id="30516">Еще видео от %s</string>
+    <string id="30517">Искать по автору</string>
+    <string id="30518">Видео ID</string>
+    <string id="30519">Ввведите имя контакта</string>
+    <string id="30520">Воспроизвести в 1080p</string>
+    <string id="30521">Воспроизвести в 720p</string>
+    <string id="30522">Воспроизвести в SD</string>
+    <string id="30523">%s Disco!</string>
+    <string id="30524">Изменить поиск Disco</string>
+    <string id="30525">Удалить поиск Disco</string>
+    <string id="30526">Воспроизвести папку</string>
+    <string id="30527">Сейчас играет...</string>
+    <string id="30528">Изменение подписки на плейлисты</string>
+    <string id="30529">Похожие видео</string>
+
+    <!-- messages -->
+    <string id="30600">Ошибка</string>
+    <string id="30601">Нет результатов!</string>
+    <string id="30602">Нет новых</string>
+    <string id="30603">Воспроизведение не удалось</string>
+    <string id="30604">Загрузка завершена</string>
+    <string id="30605">YouTube не отвечает</string>
+    <string id="30606">Неизвестная ошибка:</string>
+    <string id="30607">Нет подходящего разрешения</string>
+    <string id="30608">Только для совершеннолетних!</string>
+    <string id="30609">Ошибка входа</string>
+    <string id="30610">Уточнение очищено</string>
+    <string id="30611">Папка для загрузок не задана</string>
+    <string id="30612">Начинаю закачку</string>
+    <string id="30613">Контакт добавлен</string>
+    <string id="30614">Контакт удален</string>
+    <string id="30615">Не найдено</string>
+    <string id="30616">Уточнение </string>
+    <string id="30617">Неизвестная ошибка</string>
+    <string id="30618">Не удается извлечь адрес видео</string>
+    <string id="30619">Не удалось очистить YouTube</string>
+    <string id="30620">Формат не поддерживается</string>
+    <string id="30621">Неправильное имя или пароль</string>
+    <string id="30622">Воспроизведение не удалось, попробуйте войти в аккаунт</string>
+    <string id="30623">Достигнуто максимальное колличество попыток входа.</string>
+</strings>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/resources/settings.xml	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<settings>
+
+  <!-- General -->
+  <category label="30218">
+    <setting id="username" type="text" label="30200" default="" />
+    <setting id="user_password" type="text" option="hidden" label="30201" enable="!eq(-1,)" default="" />
+    <setting type="sep" />
+    <setting id="safe_search" type="enum" label="30209" lvalues="30277|30219|30220" default="1" />    
+    <setting id="hd_videos" type="enum" label="30208" lvalues="30213|30215|30216|30217" default="2" />
+    <setting type="sep" />
+    <setting id="download_path" type="folder" label="30207" default="" />
+    <setting type="sep" />
+    <setting id="lang_code" type="enum" label="30276" lvalues="30277|30278|30279|30280|30281|30282|30283" default="0" />
+    <setting id="transcode" type="bool" label="30274" enable="!eq(-1,0)" default="false" />
+    <setting id="annotations" type="bool" label="30284" enable="!eq(-2,0)" default="false" />
+  </category>
+
+  <!-- Advanced -->
+  <category label="30260">
+    <setting id="list_view" type="enum" label="30259" lvalues="30271|30272|30273" default="0" />
+    <setting id="perpage" type="enum" label="30210" values="10|15|20|25|30|40|50" default="6" />
+    <setting id="saved_searches" type="enum" label="30211" values="10|20|30|40" default="3" />
+    <setting type="sep" />
+    <setting id="region_id" type="enum" label="30221" lvalues="30222|30223|30224|30225|30226|30227|30228|30229|30230|30231|30232|30233|30234|30235|30236|30237|30238|30239|30240|30241|30242|30243|30244|30245" default="0" />
+    <setting id="hd_videos_download" type="enum" label="30261" lvalues="30262|30215|30216|30217" default="0" />
+    <setting type="sep" />
+    <setting id="preferred" type="bool" label="30285" default="true" />
+    <setting id="notification_length" type="enum" label="30212" values="1|2|3|4|5|6|7|8|9|10" default="2" />
+    <setting id="timeout" type="enum" label="30265" values="5|10|15|20|25" default="3" />
+    <setting id="debug" type="bool" label="30246" default="false" />
+    <setting type="sep" />    
+  </category>
+
+  <!-- Folders -->
+  <category label="30247">
+	<setting id="explore" type="bool" label="30248" default="true" />
+    <setting id="recommended" type="bool" label="30249" default="true" />
+    <setting id="watch_later" type="bool" label="30250" default="true" />
+    <setting id="liked" type="bool" label="30251" default= "true" />
+    <setting id="contacts" type="bool" label="30252" default="true" />
+    <setting id="favorites" type="bool" label="30253" default="true" />
+    <setting id="playlists" type="bool" label="30254" default="true" />
+    <setting id="subscriptions" type="bool" label="30255" default="true" />
+    <setting id="uploads" type="bool" label="30256" default="true" />
+    <setting id="downloads" type="bool" label="30257" default="true" />
+    <setting id="search" type="bool" label="30258" default="true" />
+    <setting id="history" type="bool" label="30288" default="true" />
+  </category>
+
+  <!-- Explore YouTube -->
+  <category label="30270">
+    <setting id="categories" type="bool" label="30266" default="true" />
+    <setting id="disco" type="bool" label="30263" default="true" />
+    <setting id="feeds" type="bool" label="30249" default="true" />
+    <setting id="movies" type="bool" label="30268" default="true" /> 
+    <setting id="music" type="bool" label="30286" default="true" />
+    <setting id="shows" type="bool" label="30267" default="true" />
+    <setting id="trailers" type="bool" label="30264" default="true" />
+    <setting id="live" type="bool" label="30287" default="true" />
+  </category>
+</settings>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/storageserverdummy.py	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,96 @@
+'''
+     StorageServer override.
+     Version: 1.0
+'''
+import xbmc
+try:
+    import hashlib
+except:
+    import md5
+
+import xbmcvfs
+import os
+import time
+import sys
+
+if hasattr(sys.modules["__main__"], "settings"):
+    settings = sys.modules["__main__"].settings
+else:
+    settings = False
+
+
+class StorageServer:
+    def __init__(self, table=False):
+        self.table = table
+        if settings:
+            temporary_path = xbmc.translatePath(settings.getAddonInfo("profile"))
+            if not xbmcvfs.exists(temporary_path):
+                os.makedirs(temporary_path)
+
+        return None
+
+    def cacheFunction(self, funct=False, *args):
+        result = ""
+        if not settings:
+            return funct(*args)
+        elif funct and self.table:
+            name = repr(funct)
+            if name.find(" of ") > -1:
+                name = name[name.find("method") + 7:name.find(" of ")]
+            elif name.find(" at ") > -1:
+                name = name[name.find("function") + 9:name.find(" at ")]
+
+            # Build unique name
+            if "hashlib" in globals():
+                keyhash = hashlib.md5()
+            else:
+                keyhash = md5.new()
+
+            for params in args:
+                if isinstance(params, dict):
+                    for key in sorted(params.iterkeys()):
+                        if key not in ["new_results_function"]:
+                            keyhash.update("'%s'='%s'" % (key, params[key]))
+                elif isinstance(params, list):
+                    keyhash.update(",".join(["%s" % el for el in params]))
+                else:
+                    try:
+                        keyhash.update(params)
+                    except:
+                        keyhash.update(str(params))
+
+            name += "-" + keyhash.hexdigest() + ".cache"
+
+            path = os.path.join(xbmc.translatePath(settings.getAddonInfo("profile")).decode("utf-8"), name)
+            if xbmcvfs.exists(path) and os.path.getmtime(path) > time.time() - 3600:
+                print "Getting cache : " + repr(path)
+                temp = open(path)
+                result = eval(temp.read())
+                temp.close()
+            else:
+                print "Setting cache: " + repr(path)
+                result = funct(*args)
+                if len(result) > 0:
+                    temp = open(path, "w")
+                    temp.write(repr(result))
+                    temp.close()
+
+        return result
+
+    def set(self, name, data):
+        return ""
+
+    def get(self, name):
+        return ""
+
+    def setMulti(self, name, data):
+        return ""
+
+    def getMulti(self, name, items):
+        return ""
+
+    def lock(self, name):
+        return False
+
+    def unlock(self, name):
+        return False
Binary file xbmc/plugin.video.youtube/thumbnails/add_user.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/contacts.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/default.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/discoball.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/downloads.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/explore.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/favorites.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/featured.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/feeds.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/history.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/liked.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/live.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/login.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/most.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/movies.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/music.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/newsubscriptions.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/next.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/playbyid.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/playlists.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/previous.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/recommended.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/search.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/settings.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/shows.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/subscriptions.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/top.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/trailers.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/uploads.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/user.png has changed
Binary file xbmc/plugin.video.youtube/thumbnails/watch_later.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/plugin.video.youtube/xbmcvfsdummy.py	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,20 @@
+'''
+     XBMCVFS override for Dharma.
+     Version: 1.0
+'''
+
+import os
+
+
+def exists(target):
+    return os.path.exists(target)
+
+
+def rename(origin, target):
+    return os.rename(origin, target)
+
+
+def delete(target):
+    if os.path.isfile(target) and not os.path.isdir(target):
+        return os.unlink(target)
+    return False
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/script.module.parsedom/addon.xml	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,11 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
+<addon id='script.module.parsedom' version='1.5.0' name='Parsedom for xbmc plugins' provider-name='TheCollective'>
+  <requires>
+    <import addon='xbmc.python' version='2.0'/>
+  </requires>
+  <extension point='xbmc.addon.metadata'>
+    <platform>all</platform>
+    <summary lang='en'>Parsedom for xbmc plugins.</summary>
+  </extension>
+  <extension point='xbmc.python.module' library='lib' />
+</addon>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/script.module.parsedom/changelog.txt	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,30 @@
+[B]Version 1.5.0[/B]
+- Fixed: proper fix for getParameters that only affects Frodo branch
+- Added: new function to get the version of xbmc as a float
+
+
+[B]Version 1.4.0[/B]
+- Special fix for eden branch to unbreak changes for Frodo
+
+[B]Version 1.3.0[/B]
+- Team xbmc decided to stop unquote-ing their path strings, so getParams now does it for them
+
+[B]Version 1.2.0[/B]
+- fetchPage should default to utf-8 encoding
+- parseDOM should handle utf-8 encoding
+
+[B]Version 1.1.0[/B]
+- Handle \t that breaks DOM variable extraction
+- Added extractJS function
+
+[B]Version 1.0.0[/B]
+- Minor fixes
+
+[B]Version 0.9.1[/B]
+- Stability and more functions
+- Add cookie support to fetchPage.
+- Add getCookieInfoAsHTML.
+- Add POST and Refering capabilities to fetchPage
+
+[B]Version 0.9.0[/B]
+- Initial public test run.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/script.module.parsedom/lib/CommonFunctions.py	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,537 @@
+'''
+   Parsedom for XBMC plugins
+   Copyright (C) 2010-2011 Tobias Ussing And Henrik Mosgaard Jensen
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import urllib
+import urllib2
+import re
+import io
+import inspect
+import time
+import HTMLParser
+#import chardet
+import json
+
+version = u"1.4.0"
+plugin = u"CommonFunctions-" + version
+print plugin
+
+USERAGENT = u"Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1"
+
+if hasattr(sys.modules["__main__"], "xbmc"):
+    xbmc = sys.modules["__main__"].xbmc
+else:
+    import xbmc
+
+if hasattr(sys.modules["__main__"], "xbmcgui"):
+    xbmcgui = sys.modules["__main__"].xbmcgui
+else:
+    import xbmcgui
+
+if hasattr(sys.modules["__main__"], "dbg"):
+    dbg = sys.modules["__main__"].dbg
+else:
+    dbg = False
+
+if hasattr(sys.modules["__main__"], "dbglevel"):
+    dbglevel = sys.modules["__main__"].dbglevel
+else:
+    dbglevel = 3
+
+if hasattr(sys.modules["__main__"], "opener"):
+    urllib2.install_opener(sys.modules["__main__"].opener)
+
+
+# This function raises a keyboard for user input
+def getUserInput(title=u"Input", default=u"", hidden=False):
+    log("", 5)
+    result = None
+
+    # Fix for when this functions is called with default=None
+    if not default:
+        default = u""
+
+    keyboard = xbmc.Keyboard(default, title)
+    keyboard.setHiddenInput(hidden)
+    keyboard.doModal()
+
+    if keyboard.isConfirmed():
+        result = keyboard.getText()
+
+    log(repr(result), 5)
+    return result
+
+
+# This function raises a keyboard numpad for user input
+def getUserInputNumbers(title=u"Input", default=u""):
+    log("", 5)
+    result = None
+
+    # Fix for when this functions is called with default=None
+    if not default:
+        default = u""
+
+    keyboard = xbmcgui.Dialog()
+    result = keyboard.numeric(0, title, default)
+
+    log(repr(result), 5)
+    return str(result)
+
+
+# Converts the request url passed on by xbmc to the plugin into a dict of key-value pairs
+def getParameters(parameterString):
+    log("", 5)
+    commands = {}
+    splitCommands = parameterString[parameterString.find('?') + 1:].split('&')
+
+    for command in splitCommands:
+        if (len(command) > 0):
+            splitCommand = command.split('=')
+            key = splitCommand[0]
+            value = splitCommand[1]
+            commands[key] = value
+
+    log(repr(commands), 5)
+    return commands
+
+
+def replaceHTMLCodes(txt):
+    log(repr(txt), 5)
+
+    # Fix missing ; in &#<number>;
+    txt = re.sub("(&#[0-9]+)([^;^0-9]+)", "\\1;\\2", makeUTF8(txt))
+
+    txt = HTMLParser.HTMLParser().unescape(txt)
+    txt = txt.replace("&amp;", "&")
+    log(repr(txt), 5)
+    return txt
+
+
+def stripTags(html):
+    log(repr(html), 5)
+    sub_start = html.find("<")
+    sub_end = html.find(">")
+    while sub_start < sub_end and sub_start > -1:
+        html = html.replace(html[sub_start:sub_end + 1], "").strip()
+        sub_start = html.find("<")
+        sub_end = html.find(">")
+
+    log(repr(html), 5)
+    return html
+
+
+def _getDOMContent(html, name, match, ret):  # Cleanup
+    log("match: " + match, 3)
+
+    endstr = u"</" + name  # + ">"
+
+    start = html.find(match)
+    end = html.find(endstr, start)
+    pos = html.find("<" + name, start + 1 )
+
+    log(str(start) + " < " + str(end) + ", pos = " + str(pos) + ", endpos: " + str(end), 8)
+
+    while pos < end and pos != -1:  # Ignore too early </endstr> return
+        tend = html.find(endstr, end + len(endstr))
+        if tend != -1:
+            end = tend
+        pos = html.find("<" + name, pos + 1)
+        log("loop: " + str(start) + " < " + str(end) + " pos = " + str(pos), 8)
+
+    log("start: %s, len: %s, end: %s" % (start, len(match), end), 3)
+    if start == -1 and end == -1:
+        result = u""
+    elif start > -1 and end > -1:
+        result = html[start + len(match):end]
+    elif end > -1:
+        result = html[:end]
+    elif start > -1:
+        result = html[start + len(match):]
+
+    if ret:
+        endstr = html[end:html.find(">", html.find(endstr)) + 1]
+        result = match + result + endstr
+
+    log("done result length: " + str(len(result)), 3)
+    return result
+
+def _getDOMAttributes(match, name, ret):
+    log("", 3)
+    lst = re.compile('<' + name + '.*?' + ret + '=(.[^>]*?)>', re.M | re.S).findall(match)
+    ret = []
+    for tmp in lst:
+        cont_char = tmp[0]
+        if cont_char in "'\"":
+            log("Using %s as quotation mark" % cont_char, 3)
+
+            # Limit down to next variable.
+            if tmp.find('=' + cont_char, tmp.find(cont_char, 1)) > -1:
+                tmp = tmp[:tmp.find('=' + cont_char, tmp.find(cont_char, 1))]
+
+            # Limit to the last quotation mark
+            if tmp.rfind(cont_char, 1) > -1:
+                tmp = tmp[1:tmp.rfind(cont_char)]
+        else:
+            log("No quotation mark found", 3)
+            if tmp.find(" ") > 0:
+                tmp = tmp[:tmp.find(" ")]
+            elif tmp.find("/") > 0:
+                tmp = tmp[:tmp.find("/")]
+            elif tmp.find(">") > 0:
+                tmp = tmp[:tmp.find(">")]
+
+        ret.append(tmp.strip())
+
+    log("Done: " + repr(ret), 3)
+    return ret
+
+def _getDOMElements(item, name, attrs):
+    log("", 3)
+    lst = []
+    for key in attrs:
+        lst2 = re.compile('(<' + name + '[^>]*?(?:' + key + '=[\'"]' + attrs[key] + '[\'"].*?>))', re.M | re.S).findall(item)
+        if len(lst2) == 0 and attrs[key].find(" ") == -1:  # Try matching without quotation marks
+            lst2 = re.compile('(<' + name + '[^>]*?(?:' + key + '=' + attrs[key] + '.*?>))', re.M | re.S).findall(item)
+
+        if len(lst) == 0:
+            log("Setting main list " + repr(lst2), 5)
+            lst = lst2
+            lst2 = []
+        else:
+            log("Setting new list " + repr(lst2), 5)
+            test = range(len(lst))
+            test.reverse()
+            for i in test:  # Delete anything missing from the next list.
+                if not lst[i] in lst2:
+                    log("Purging mismatch " + str(len(lst)) + " - " + repr(lst[i]), 3)
+                    del(lst[i])
+
+    if len(lst) == 0 and attrs == {}:
+        log("No list found, trying to match on name only", 3)
+        lst = re.compile('(<' + name + '>)', re.M | re.S).findall(item)
+        if len(lst) == 0:
+            lst = re.compile('(<' + name + ' .*?>)', re.M | re.S).findall(item)
+
+    log("Done: " + str(type(lst)), 3)
+    return lst
+
+def parseDOM(html, name=u"", attrs={}, ret=False):
+    log("Name: " + repr(name) + " - Attrs:" + repr(attrs) + " - Ret: " + repr(ret) + " - HTML: " + str(type(html)), 3)
+    #log("BLA: " + repr(type(html)) + " - " + repr(type(name)))
+
+    if isinstance(name, str): # Should be handled
+        try:
+            name = name #.decode("utf-8")
+        except:
+            log("Couldn't decode name binary string: " + repr(name))
+
+    if isinstance(html, str):
+        try:
+            html = [html.decode("utf-8")] # Replace with chardet thingy
+        except:
+            log("Couldn't decode html binary string. Data length: " + repr(len(html)))
+            html = [html]
+    elif isinstance(html, unicode):
+        html = [html]
+    elif not isinstance(html, list):
+        log("Input isn't list or string/unicode.")
+        return u""
+
+    if not name.strip():
+        log("Missing tag name")
+        return u""
+
+    ret_lst = []
+    for item in html:
+        temp_item = re.compile('(<[^>]*?\n[^>]*?>)').findall(item)
+        for match in temp_item:
+            item = item.replace(match, match.replace("\n", " "))
+
+        lst = _getDOMElements(item, name, attrs)
+
+        if isinstance(ret, str):
+            log("Getting attribute %s content for %s matches " % (ret, len(lst) ), 3)
+            lst2 = []
+            for match in lst:
+                lst2 += _getDOMAttributes(match, name, ret)
+            lst = lst2
+        else:
+            log("Getting element content for %s matches " % len(lst), 3)
+            lst2 = []
+            for match in lst:
+                log("Getting element content for %s" % match, 4)
+                temp = _getDOMContent(item, name, match, ret).strip()
+                item = item[item.find(temp, item.find(match)) + len(temp):]
+                lst2.append(temp)
+            lst = lst2
+        ret_lst += lst
+
+    log("Done: " + repr(ret_lst), 3)
+    return ret_lst
+
+
+def extractJS(data, function=False, variable=False, match=False, evaluate=False, values=False):
+    log("")
+    scripts = parseDOM(data, "script")
+    if len(scripts) == 0:
+        log("Couldn't find any script tags. Assuming javascript file was given.")
+        scripts = [data]
+
+    lst = []
+    log("Extracting", 4)
+    for script in scripts:
+        tmp_lst = []
+        if function:
+            tmp_lst = re.compile(function + '\(.*?\).*?;', re.M | re.S).findall(script)
+        elif variable:
+            tmp_lst = re.compile(variable + '[ ]+=.*?;', re.M | re.S).findall(script)            
+        else:
+            tmp_lst = [script]
+        if len(tmp_lst) > 0:
+            log("Found: " + repr(tmp_lst), 4)
+            lst += tmp_lst
+        else:
+            log("Found nothing on: " + script, 4)
+
+    test = range(0, len(lst))
+    test.reverse()
+    for i in test:
+        if match and lst[i].find(match) == -1:
+            log("Removing item: " + repr(lst[i]), 10)
+            del lst[i]
+        else:
+            log("Cleaning item: " + repr(lst[i]), 4)
+            if lst[i][0] == u"\n":
+                lst[i] == lst[i][1:]
+            if lst[i][len(lst) -1] == u"\n":
+                lst[i] == lst[i][:len(lst)- 2]
+            lst[i] = lst[i].strip()
+
+    if values or evaluate:
+        for i in range(0, len(lst)):
+            log("Getting values %s" % lst[i])
+            if function:
+                if evaluate: # include the ( ) for evaluation
+                    data = re.compile("(\(.*?\))", re.M | re.S).findall(lst[i])
+                else:
+                    data = re.compile("\((.*?)\)", re.M | re.S).findall(lst[i])
+            elif variable:
+                tlst = re.compile(variable +".*?=.*?;", re.M | re.S).findall(lst[i])
+                data = []
+                for tmp in tlst: # This breaks for some stuff. "ad_tag": "http://ad-emea.doubleclick.net/N4061/pfadx/com.ytpwatch.entertainment/main_563326'' # ends early, must end with } 
+                    cont_char = tmp[0]
+                    cont_char = tmp[tmp.find("=") + 1:].strip()
+                    cont_char = cont_char[0]
+                    if cont_char in "'\"":
+                        log("Using %s as quotation mark" % cont_char, 1)
+                        tmp = tmp[tmp.find(cont_char) + 1:tmp.rfind(cont_char)]
+                    else:
+                        log("No quotation mark found", 1)
+                        tmp = tmp[tmp.find("=") + 1: tmp.rfind(";")]
+
+                    tmp = tmp.strip()
+                    if len(tmp) > 0:
+                        data.append(tmp)
+            else:
+                log("ERROR: Don't know what to extract values from")
+
+            log("Values extracted: %s" % repr(data))
+            if len(data) > 0:
+                lst[i] = data[0]
+
+    if evaluate:
+        for i in range(0, len(lst)):
+            log("Evaluating %s" % lst[i])
+            data = lst[i].strip()
+            try:
+                try:
+                    lst[i] = json.loads(data)
+                except:
+                    log("Couldn't json.loads, trying eval")
+                    lst[i] = eval(data)
+            except:
+                log("Couldn't eval: %s from %s" % (repr(data), repr(lst[i])))
+
+    log("Done: " + str(len(lst)))
+    return lst
+
+def fetchPage(params={}):
+    get = params.get
+    link = get("link")
+    ret_obj = {}
+    if get("post_data"):
+        log("called for : " + repr(params['link']))
+    else:
+        log("called for : " + repr(params))
+
+    if not link or int(get("error", "0")) > 2:
+        log("giving up")
+        ret_obj["status"] = 500
+        return ret_obj
+
+    if get("post_data"):
+        if get("hide_post_data"):
+            log("Posting data", 2)
+        else:
+            log("Posting data: " + urllib.urlencode(get("post_data")), 2)
+
+        request = urllib2.Request(link, urllib.urlencode(get("post_data")))
+        request.add_header('Content-Type', 'application/x-www-form-urlencoded')
+    else:
+        log("Got request", 2)
+        request = urllib2.Request(link)
+
+    if get("headers"):
+        for head in get("headers"):
+            request.add_header(head[0], head[1])
+
+    request.add_header('User-Agent', USERAGENT)
+
+    if get("cookie"):
+        request.add_header('Cookie', get("cookie"))
+
+    if get("refering"):
+        request.add_header('Referer', get("refering"))
+
+    try:
+        log("connecting to server...", 1)
+
+        con = urllib2.urlopen(request)
+        ret_obj["header"] = con.info()
+        ret_obj["new_url"] = con.geturl()
+        if get("no-content", "false") == u"false" or get("no-content", "false") == "false":
+            inputdata = con.read()
+            #data_type = chardet.detect(inputdata)
+            #inputdata = inputdata.decode(data_type["encoding"])
+            ret_obj["content"] = inputdata.decode("utf-8")
+
+        con.close()
+
+        log("Done")
+        ret_obj["status"] = 200
+        return ret_obj
+
+    except urllib2.HTTPError, e:
+        err = str(e)
+        log("HTTPError : " + err)
+        log("HTTPError - Headers: " + str(e.headers) + " - Content: " + e.fp.read())
+
+        params["error"] = str(int(get("error", "0")) + 1)
+        ret = fetchPage(params)
+
+        if not "content" in ret and e.fp:
+            ret["content"] = e.fp.read()
+            return ret
+
+        ret_obj["status"] = 500
+        return ret_obj
+
+    except urllib2.URLError, e:
+        err = str(e)
+        log("URLError : " + err)
+
+        time.sleep(3)
+        params["error"] = str(int(get("error", "0")) + 1)
+        ret_obj = fetchPage(params)
+        return ret_obj
+
+
+def getCookieInfoAsHTML():
+    log("", 5)
+    if hasattr(sys.modules["__main__"], "cookiejar"):
+        cookiejar = sys.modules["__main__"].cookiejar
+
+        cookie = repr(cookiejar)
+        cookie = cookie.replace("<_LWPCookieJar.LWPCookieJar[", "")
+        cookie = cookie.replace("), Cookie(version=0,", "></cookie><cookie ")
+        cookie = cookie.replace(")]>", "></cookie>")
+        cookie = cookie.replace("Cookie(version=0,", "<cookie ")
+        cookie = cookie.replace(", ", " ")
+        log(repr(cookie), 5)
+        return cookie
+
+    log("Found no cookie", 5)
+    return ""
+
+
+# This function implements a horrible hack related to python 2.4's terrible unicode handling.
+def makeAscii(data):
+    log(repr(data), 5)
+    #if sys.hexversion >= 0x02050000:
+    #        return data
+
+    try:
+        return data.encode('ascii', "ignore")
+    except:
+        log("Hit except on : " + repr(data))
+        s = u""
+        for i in data:
+            try:
+                i.encode("ascii", "ignore")
+            except:
+                log("Can't convert character", 4)
+                continue
+            else:
+                s += i
+
+        log(repr(s), 5)
+        return s
+
+
+# This function handles stupid utf handling in python.
+def makeUTF8(data):
+    log(repr(data), 5)
+    return data
+    try:
+        return data.decode('utf8', 'xmlcharrefreplace') # was 'ignore'
+    except:
+        log("Hit except on : " + repr(data))
+        s = u""
+        for i in data:
+            try:
+                i.decode("utf8", "xmlcharrefreplace") 
+            except:
+                log("Can't convert character", 4)
+                continue
+            else:
+                s += i
+        log(repr(s), 5)
+        return s
+
+
+def openFile(filepath, options=u"r"):
+    log(repr(filepath) + " - " + repr(options))
+    if options.find("b") == -1:  # Toggle binary mode on failure
+        alternate = options + u"b"
+    else:
+        alternate = options.replace(u"b", u"")
+
+    try:
+        log("Trying normal: %s" % options)
+        return io.open(filepath, options)
+    except:
+        log("Fallback to binary: %s" % alternate)
+        return io.open(filepath, alternate)
+
+
+def log(description, level=0):
+    if dbg and dbglevel > level:
+        try:
+            xbmc.log((u"[%s] %s : '%s'" % (plugin, inspect.stack()[1][3], description)).decode("utf-8"), xbmc.LOGNOTICE)
+        except:
+            xbmc.log(u"FALLBACK [%s] %s : '%s'" % (plugin, inspect.stack()[1][3], repr(description)), xbmc.LOGNOTICE)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xbmc/script.module.parsedom/lib/CommonFunctions.py.bak	Mon Dec 31 16:45:52 2012 +0100
@@ -0,0 +1,557 @@
+'''
+   Parsedom for XBMC plugins
+   Copyright (C) 2010-2011 Tobias Ussing And Henrik Mosgaard Jensen
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import urllib
+import urllib2
+import re
+import io
+import inspect
+import time
+import HTMLParser
+#import chardet
+import json
+
+version = u"1.5.0"
+plugin = u"CommonFunctions Beta-" + version
+print plugin
+
+USERAGENT = u"Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1"
+
+if hasattr(sys.modules["__main__"], "xbmc"):
+    xbmc = sys.modules["__main__"].xbmc
+else:
+    import xbmc
+
+if hasattr(sys.modules["__main__"], "xbmcgui"):
+    xbmcgui = sys.modules["__main__"].xbmcgui
+else:
+    import xbmcgui
+
+if hasattr(sys.modules["__main__"], "dbg"):
+    dbg = sys.modules["__main__"].dbg
+else:
+    dbg = False
+
+if hasattr(sys.modules["__main__"], "dbglevel"):
+    dbglevel = sys.modules["__main__"].dbglevel
+else:
+    dbglevel = 3
+
+if hasattr(sys.modules["__main__"], "opener"):
+    urllib2.install_opener(sys.modules["__main__"].opener)
+
+
+# This function raises a keyboard for user input
+def getUserInput(title=u"Input", default=u"", hidden=False):
+    log("", 5)
+    result = None
+
+    # Fix for when this functions is called with default=None
+    if not default:
+        default = u""
+
+    keyboard = xbmc.Keyboard(default, title)
+    keyboard.setHiddenInput(hidden)
+    keyboard.doModal()
+
+    if keyboard.isConfirmed():
+        result = keyboard.getText()
+
+    log(repr(result), 5)
+    return result
+
+
+# This function raises a keyboard numpad for user input
+def getUserInputNumbers(title=u"Input", default=u""):
+    log("", 5)
+    result = None
+
+    # Fix for when this functions is called with default=None
+    if not default:
+        default = u""
+
+    keyboard = xbmcgui.Dialog()
+    result = keyboard.numeric(0, title, default)
+
+    log(repr(result), 5)
+    return str(result)
+
+
+def getXBMCVersion():
+    log("", 3)
+    version = xbmc.getInfoLabel( "System.BuildVersion" )
+    log(version, 3)
+    if version.find("-") -1:
+        version = version[:version.find("-")]
+    version = float(version)
+    log(repr(version))
+    return version
+
+# Converts the request url passed on by xbmc to the plugin into a dict of key-value pairs
+def getParameters(parameterString):
+    log("", 5)
+    commands = {}
+    if getXBMCVersion() >= 12.0:
+        parameterString = urllib.unquote_plus(parameterString)
+    splitCommands = parameterString[parameterString.find('?') + 1:].split('&')
+
+    for command in splitCommands:
+        if (len(command) > 0):
+            splitCommand = command.split('=')
+            key = splitCommand[0]
+            try: 
+                value = splitCommand[1].encode("utf-8")
+            except:
+                log("Error utf-8 encoding argument value: " + repr(splitCommand[1]))
+                value = splitCommand[1]
+
+            commands[key] = value
+
+    log(repr(commands), 5)
+    return commands
+
+
+def replaceHTMLCodes(txt):
+    log(repr(txt), 5)
+
+    # Fix missing ; in &#<number>;
+    txt = re.sub("(&#[0-9]+)([^;^0-9]+)", "\\1;\\2", makeUTF8(txt))
+
+    txt = HTMLParser.HTMLParser().unescape(txt)
+    txt = txt.replace("&amp;", "&")
+    log(repr(txt), 5)
+    return txt
+
+
+def stripTags(html):
+    log(repr(html), 5)
+    sub_start = html.find("<")
+    sub_end = html.find(">")
+    while sub_start < sub_end and sub_start > -1:
+        html = html.replace(html[sub_start:sub_end + 1], "").strip()
+        sub_start = html.find("<")
+        sub_end = html.find(">")
+
+    log(repr(html), 5)
+    return html
+
+
+def _getDOMContent(html, name, match, ret):  # Cleanup
+    log("match: " + match, 3)
+
+    endstr = u"</" + name  # + ">"
+
+    start = html.find(match)
+    end = html.find(endstr, start)
+    pos = html.find("<" + name, start + 1 )
+
+    log(str(start) + " < " + str(end) + ", pos = " + str(pos) + ", endpos: " + str(end), 8)
+
+    while pos < end and pos != -1:  # Ignore too early </endstr> return
+        tend = html.find(endstr, end + len(endstr))
+        if tend != -1:
+            end = tend
+        pos = html.find("<" + name, pos + 1)
+        log("loop: " + str(start) + " < " + str(end) + " pos = " + str(pos), 8)
+
+    log("start: %s, len: %s, end: %s" % (start, len(match), end), 3)
+    if start == -1 and end == -1:
+        result = u""
+    elif start > -1 and end > -1:
+        result = html[start + len(match):end]
+    elif end > -1:
+        result = html[:end]
+    elif start > -1:
+        result = html[start + len(match):]
+
+    if ret:
+        endstr = html[end:html.find(">", html.find(endstr)) + 1]
+        result = match + result + endstr
+
+    log("done result length: " + str(len(result)), 3)
+    return result
+
+def _getDOMAttributes(match, name, ret):
+    log("", 3)
+
+    lst = re.compile('<' + name + '.*?' + ret + '=([\'"].[^>]*?[\'"])>', re.M | re.S).findall(match)
+    if len(lst) == 0:
+        lst = re.compile('<' + name + '.*?' + ret + '=(.[^>]*?)>', re.M | re.S).findall(match)
+    ret = []
+    for tmp in lst:
+        cont_char = tmp[0]
+        if cont_char in "'\"":
+            log("Using %s as quotation mark" % cont_char, 3)
+
+            # Limit down to next variable.
+            if tmp.find('=' + cont_char, tmp.find(cont_char, 1)) > -1:
+                tmp = tmp[:tmp.find('=' + cont_char, tmp.find(cont_char, 1))]
+
+            # Limit to the last quotation mark
+            if tmp.rfind(cont_char, 1) > -1:
+                tmp = tmp[1:tmp.rfind(cont_char)]
+        else:
+            log("No quotation mark found", 3)
+            if tmp.find(" ") > 0:
+                tmp = tmp[:tmp.find(" ")]
+            elif tmp.find("/") > 0:
+                tmp = tmp[:tmp.find("/")]
+            elif tmp.find(">") > 0:
+                tmp = tmp[:tmp.find(">")]
+
+        ret.append(tmp.strip())
+
+    log("Done: " + repr(ret), 3)
+    return ret
+
+def _getDOMElements(item, name, attrs):
+    log("", 3)
+
+    lst = []
+    for key in attrs:
+        lst2 = re.compile('(<' + name + '[^>]*?(?:' + key + '=[\'"]' + attrs[key] + '[\'"].*?>))', re.M | re.S).findall(item)
+        if len(lst2) == 0 and attrs[key].find(" ") == -1:  # Try matching without quotation marks
+            lst2 = re.compile('(<' + name + '[^>]*?(?:' + key + '=' + attrs[key] + '.*?>))', re.M | re.S).findall(item)
+
+        if len(lst) == 0:
+            log("Setting main list " + repr(lst2), 5)
+            lst = lst2
+            lst2 = []
+        else:
+            log("Setting new list " + repr(lst2), 5)
+            test = range(len(lst))
+            test.reverse()
+            for i in test:  # Delete anything missing from the next list.
+                if not lst[i] in lst2:
+                    log("Purging mismatch " + str(len(lst)) + " - " + repr(lst[i]), 3)
+                    del(lst[i])
+
+    if len(lst) == 0 and attrs == {}:
+        log("No list found, trying to match on name only", 3)
+        lst = re.compile('(<' + name + '>)', re.M | re.S).findall(item)
+        if len(lst) == 0:
+            lst = re.compile('(<' + name + ' .*?>)', re.M | re.S).findall(item)
+
+    log("Done: " + str(type(lst)), 3)
+    return lst
+
+def parseDOM(html, name=u"", attrs={}, ret=False):
+    log("Name: " + repr(name) + " - Attrs:" + repr(attrs) + " - Ret: " + repr(ret) + " - HTML: " + str(type(html)), 3)
+
+    if isinstance(name, str): # Should be handled
+        try:
+            name = name #.decode("utf-8")
+        except:
+            log("Couldn't decode name binary string: " + repr(name))
+
+    if isinstance(html, str):
+        try:
+            html = [html.decode("utf-8")] # Replace with chardet thingy
+        except:
+            log("Couldn't decode html binary string. Data length: " + repr(len(html)))
+            html = [html]
+    elif isinstance(html, unicode):
+        html = [html]
+    elif not isinstance(html, list):
+        log("Input isn't list or string/unicode.")
+        return u""
+
+    if not name.strip():
+        log("Missing tag name")
+        return u""
+
+    ret_lst = []
+    for item in html:
+        temp_item = re.compile('(<[^>]*?\n[^>]*?>)').findall(item)
+        for match in temp_item:
+            item = item.replace(match, match.replace("\n", " "))
+
+        lst = _getDOMElements(item, name, attrs)
+
+        if isinstance(ret, str):
+            log("Getting attribute %s content for %s matches " % (ret, len(lst) ), 3)
+            lst2 = []
+            for match in lst:
+                lst2 += _getDOMAttributes(match, name, ret)
+            lst = lst2
+        else:
+            log("Getting element content for %s matches " % len(lst), 3)
+            lst2 = []
+            for match in lst:
+                log("Getting element content for %s" % match, 4)
+                temp = _getDOMContent(item, name, match, ret).strip()
+                item = item[item.find(temp, item.find(match)) + len(temp):]
+                lst2.append(temp)
+            lst = lst2
+        ret_lst += lst
+
+    log("Done: " + repr(ret_lst), 3)
+    return ret_lst
+
+
+def extractJS(data, function=False, variable=False, match=False, evaluate=False, values=False):
+    log("")
+    scripts = parseDOM(data, "script")
+    if len(scripts) == 0:
+        log("Couldn't find any script tags. Assuming javascript file was given.")
+        scripts = [data]
+
+    lst = []
+    log("Extracting", 4)
+    for script in scripts:
+        tmp_lst = []
+        if function:
+            tmp_lst = re.compile(function + '\(.*?\).*?;', re.M | re.S).findall(script)
+        elif variable:
+            tmp_lst = re.compile(variable + '[ ]+=.*?;', re.M | re.S).findall(script)            
+        else:
+            tmp_lst = [script]
+        if len(tmp_lst) > 0:
+            log("Found: " + repr(tmp_lst), 4)
+            lst += tmp_lst
+        else:
+            log("Found nothing on: " + script, 4)
+
+    test = range(0, len(lst))
+    test.reverse()
+    for i in test:
+        if match and lst[i].find(match) == -1:
+            log("Removing item: " + repr(lst[i]), 10)
+            del lst[i]
+        else:
+            log("Cleaning item: " + repr(lst[i]), 4)
+            if lst[i][0] == u"\n":
+                lst[i] == lst[i][1:]
+            if lst[i][len(lst) -1] == u"\n":
+                lst[i] == lst[i][:len(lst)- 2]
+            lst[i] = lst[i].strip()
+
+    if values or evaluate:
+        for i in range(0, len(lst)):
+            log("Getting values %s" % lst[i])
+            if function:
+                if evaluate: # include the ( ) for evaluation
+                    data = re.compile("(\(.*?\))", re.M | re.S).findall(lst[i])
+                else:
+                    data = re.compile("\((.*?)\)", re.M | re.S).findall(lst[i])
+            elif variable:
+                tlst = re.compile(variable +".*?=.*?;", re.M | re.S).findall(lst[i])
+                data = []
+                for tmp in tlst: # This breaks for some stuff. "ad_tag": "http://ad-emea.doubleclick.net/N4061/pfadx/com.ytpwatch.entertainment/main_563326'' # ends early, must end with } 
+                    cont_char = tmp[0]
+                    cont_char = tmp[tmp.find("=") + 1:].strip()
+                    cont_char = cont_char[0]
+                    if cont_char in "'\"":
+                        log("Using %s as quotation mark" % cont_char, 1)
+                        tmp = tmp[tmp.find(cont_char) + 1:tmp.rfind(cont_char)]
+                    else:
+                        log("No quotation mark found", 1)
+                        tmp = tmp[tmp.find("=") + 1: tmp.rfind(";")]
+
+                    tmp = tmp.strip()
+                    if len(tmp) > 0:
+                        data.append(tmp)
+            else:
+                log("ERROR: Don't know what to extract values from")
+
+            log("Values extracted: %s" % repr(data))
+            if len(data) > 0:
+                lst[i] = data[0]
+
+    if evaluate:
+        for i in range(0, len(lst)):
+            log("Evaluating %s" % lst[i])
+            data = lst[i].strip()
+            try:
+                try:
+                    lst[i] = json.loads(data)
+                except:
+                    log("Couldn't json.loads, trying eval")
+                    lst[i] = eval(data)
+            except:
+                log("Couldn't eval: %s from %s" % (repr(data), repr(lst[i])))
+
+    log("Done: " + str(len(lst)))
+    return lst
+
+def fetchPage(params={}):
+    get = params.get
+    link = get("link")
+    ret_obj = {}
+    if get("post_data"):
+        log("called for : " + repr(params['link']))
+    else:
+        log("called for : " + repr(params))
+
+    if not link or int(get("error", "0")) > 2:
+        log("giving up")
+        ret_obj["status"] = 500
+        return ret_obj
+
+    if get("post_data"):
+        if get("hide_post_data"):
+            log("Posting data", 2)
+        else:
+            log("Posting data: " + urllib.urlencode(get("post_data")), 2)
+
+        request = urllib2.Request(link, urllib.urlencode(get("post_data")))
+        request.add_header('Content-Type', 'application/x-www-form-urlencoded')
+    else:
+        log("Got request", 2)
+        request = urllib2.Request(link)
+
+    if get("headers"):
+        for head in get("headers"):
+            request.add_header(head[0], head[1])
+
+    request.add_header('User-Agent', USERAGENT)
+
+    if get("cookie"):
+        request.add_header('Cookie', get("cookie"))
+
+    if get("refering"):
+        request.add_header('Referer', get("refering"))
+
+    try:
+        log("connecting to server...", 1)
+
+        con = urllib2.urlopen(request)
+        ret_obj["header"] = con.info()
+        ret_obj["new_url"] = con.geturl()
+        if get("no-content", "false") == u"false" or get("no-content", "false") == "false":
+            inputdata = con.read()
+            #data_type = chardet.detect(inputdata)
+            #inputdata = inputdata.decode(data_type["encoding"])
+            ret_obj["content"] = inputdata.decode("utf-8")
+
+        con.close()
+
+        log("Done")
+        ret_obj["status"] = 200
+        return ret_obj
+
+    except urllib2.HTTPError, e:
+        err = str(e)
+        log("HTTPError : " + err)
+        log("HTTPError - Headers: " + str(e.headers) + " - Content: " + e.fp.read())
+
+        params["error"] = str(int(get("error", "0")) + 1)
+        ret = fetchPage(params)
+
+        if not "content" in ret and e.fp:
+            ret["content"] = e.fp.read()
+            return ret
+
+        ret_obj["status"] = 500
+        return ret_obj
+
+    except urllib2.URLError, e:
+        err = str(e)
+        log("URLError : " + err)
+
+        time.sleep(3)
+        params["error"] = str(int(get("error", "0")) + 1)
+        ret_obj = fetchPage(params)
+        return ret_obj
+
+
+def getCookieInfoAsHTML():
+    log("", 5)
+    if hasattr(sys.modules["__main__"], "cookiejar"):
+        cookiejar = sys.modules["__main__"].cookiejar
+
+        cookie = repr(cookiejar)
+        cookie = cookie.replace("<_LWPCookieJar.LWPCookieJar[", "")
+        cookie = cookie.replace("), Cookie(version=0,", "></cookie><cookie ")
+        cookie = cookie.replace(")]>", "></cookie>")
+        cookie = cookie.replace("Cookie(version=0,", "<cookie ")
+        cookie = cookie.replace(", ", " ")
+        log(repr(cookie), 5)
+        return cookie
+
+    log("Found no cookie", 5)
+    return ""
+
+
+# This function implements a horrible hack related to python 2.4's terrible unicode handling.
+def makeAscii(data):
+    log(repr(data), 5)
+    #if sys.hexversion >= 0x02050000:
+    #        return data
+
+    try:
+        return data.encode('ascii', "ignore")
+    except:
+        log("Hit except on : " + repr(data))
+        s = u""
+        for i in data:
+            try:
+                i.encode("ascii", "ignore")
+            except:
+                log("Can't convert character", 4)
+                continue
+            else:
+                s += i
+
+        log(repr(s), 5)
+        return s
+
+
+# This function handles stupid utf handling in python.
+def makeUTF8(data):
+    log(repr(data), 5)
+    return data
+    try:
+        return data.decode('utf8', 'xmlcharrefreplace') # was 'ignore'
+    except:
+        log("Hit except on : " + repr(data))
+        s = u""
+        for i in data:
+            try:
+                i.decode("utf8", "xmlcharrefreplace") 
+            except:
+                log("Can't convert character", 4)
+                continue
+            else:
+                s += i
+        log(repr(s), 5)
+        return s
+
+
+def openFile(filepath, options=u"r"):
+    log(repr(filepath) + " - " + repr(options))
+    if options.find("b") == -1:  # Toggle binary mode on failure
+        alternate = options + u"b"
+    else:
+        alternate = options.replace(u"b", u"")
+
+    try:
+        log("Trying normal: %s" % options)
+        return io.open(filepath, options)
+    except:
+        log("Fallback to binary: %s" % alternate)
+        return io.open(filepath, alternate)
+
+
+def log(description, level=0):
+    if dbg and dbglevel > level:
+        try:
+            xbmc.log((u"[%s] %s : '%s'" % (plugin, inspect.stack()[1][3], description)).decode("utf-8"), xbmc.LOGNOTICE)
+        except:
+            xbmc.log(u"FALLBACK [%s] %s : '%s'" % (plugin, inspect.stack()[1][3], repr(description)), xbmc.LOGNOTICE)