├── CONTRIBUTING.md ├── COPYING ├── LICENSE-2.0.txt ├── README ├── app.build.js ├── r.js └── static ├── admin.html ├── css └── index.css ├── images ├── favicon.ico └── play.png ├── index.html └── js ├── admin-main.js ├── main.js ├── third-party ├── jquery.parseparams.js ├── lscache.js ├── parseUri.js └── require-jquery.js └── ytdl ├── auth.js ├── config.js ├── constants.js ├── globals.js ├── load.js ├── panels ├── admin.js ├── approved.js ├── embed-codes.js ├── existing.js ├── login.js ├── logout.js ├── pending.js ├── postupload.js ├── rejected.js ├── select.js ├── upload.js └── webcam.js ├── player.js └── utils.js /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute # 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | a just a few small guidelines you need to follow. 5 | 6 | 7 | ## Contributor License Agreement ## 8 | 9 | Contributions to any Google project must be accompanied by a Contributor 10 | License Agreement. This is not a copyright **assignment**, it simply gives 11 | Google permission to use and redistribute your contributions as part of the 12 | project. 13 | 14 | * If you are an individual writing original source code and you're sure you 15 | own the intellectual property, then you'll need to sign an [individual 16 | CLA][]. 17 | 18 | * If you work for a company that wants to allow you to contribute your work, 19 | then you'll need to sign a [corporate CLA][]. 20 | 21 | You generally only need to submit a CLA once, so if you've already submitted 22 | one (even if it was for a different project), you probably don't need to do it 23 | again. 24 | 25 | [individual CLA]: https://developers.google.com/open-source/cla/individual 26 | [corporate CLA]: https://developers.google.com/open-source/cla/corporate 27 | 28 | 29 | ## Submitting a patch ## 30 | 31 | 1. It's generally best to start by opening a new issue describing the bug or 32 | feature you're intending to fix. Even if you think it's relatively minor, 33 | it's helpful to know what people are working on. Mention in the initial 34 | issue that you are planning to work on that bug or feature so that it can 35 | be assigned to you. 36 | 37 | 1. Follow the normal process of [forking][] the project, and setup a new 38 | branch to work in. It's important that each group of changes be done in 39 | separate branches in order to ensure that a pull request only includes the 40 | commits related to that bug or feature. 41 | 42 | 1. Go makes it very simple to ensure properly formatted code, so always run 43 | `go fmt` on your code before committing it. You should also run 44 | [golint][] over your code. As noted in the [golint readme][], it's not 45 | strictly necessary that your code be completely "lint-free", but this will 46 | help you find common style issues. 47 | 48 | 1. Any significant changes should almost always be accompanied by tests. The 49 | project already has good test coverage, so look at some of the existing 50 | tests if you're unsure how to go about it. [gocov][] and [gocov-html][] 51 | are invaluable tools for seeing which parts of your code aren't being 52 | exercised by your tests. 53 | 54 | 1. Do your best to have [well-formed commit messages][] for each change. 55 | This provides consistency throughout the project, and ensures that commit 56 | messages are able to be formatted properly by various git tools. 57 | 58 | 1. Finally, push the commits to your fork and submit a [pull request][]. 59 | 60 | [forking]: https://help.github.com/articles/fork-a-repo 61 | [golint]: https://github.com/golang/lint 62 | [golint readme]: https://github.com/golang/lint/blob/master/README 63 | [gocov]: https://github.com/axw/gocov 64 | [gocov-html]: https://github.com/matm/gocov-html 65 | [well-formed commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html 66 | [squash]: http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits 67 | [pull request]: https://help.github.com/articles/creating-a-pull-request 68 | 69 | 70 | ## Other notes on code organization ## 71 | 72 | Currently, everything is defined in the main `github` package, with API methods 73 | broken into separate service objects. These services map directly to how 74 | the [GitHub API documentation][] is organized, so use that as your guide for 75 | where to put new methods. 76 | 77 | Sub-service (e.g. [Repo Hooks][]) implementations are split into separate files 78 | based on the APIs they provide. These files are named service_api.go (e.g. 79 | repos_hooks.go) to describe the API to service mappings. 80 | 81 | [GitHub API documentation]: http://developer.github.com/v3/ 82 | [Repo Hooks]: http://developer.github.com/v3/repos/hooks/ 83 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2012 Google, Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Please visit http://code.google.com/p/youtube-direct-lite/ for the most up to date information 2 | on deploying and using YouTube Direct Lite. 3 | 4 | If you are using a local deployment, it is recommended that you concatenate and minify all the 5 | JavaScript source files for performance reasons. You can do this by running 6 | 7 | node r.js -o app.build.js 8 | 9 | from within this top-level directory; it will output a static-min/ directory containing the 10 | optimized JavaScript, which you can then serve. More information about this process, including 11 | the dependencies you need to install to run those commands, can be found at 12 | 13 | http://requirejs.org/docs/optimization.html -------------------------------------------------------------------------------- /app.build.js: -------------------------------------------------------------------------------- 1 | ({ 2 | appDir: 'static', 3 | baseUrl: 'js', 4 | dir: 'static-min', 5 | paths: {jquery: 'empty:'}, 6 | modules: [ 7 | {name: 'main'}, 8 | {name: 'admin-main'} 9 | ] 10 | }) -------------------------------------------------------------------------------- /static/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | YouTube Direct Lite Admin 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 19 | 25 | 26 | 30 | 43 | 44 | 47 | 50 | 53 | 56 | 57 | -------------------------------------------------------------------------------- /static/css/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: Helvetica, sans-serif; 4 | } 5 | 6 | ul { 7 | padding: 0; 8 | list-style-type: none; 9 | } 10 | 11 | a { 12 | cursor: pointer; 13 | color: blue; 14 | text-decoration: none; 15 | } 16 | 17 | .hidden { 18 | display: none; 19 | } 20 | 21 | #header { 22 | height: 28px; 23 | } 24 | 25 | #profile-picture { 26 | width: 28px; 27 | height: 28px; 28 | } 29 | 30 | #display-name { 31 | position: absolute; 32 | top: 6px; 33 | left: 34px; 34 | } 35 | 36 | #logout { 37 | position: absolute; 38 | top: 6px; 39 | right: 2px; 40 | } 41 | 42 | #message { 43 | position: fixed; 44 | top: 0; 45 | left: 50%; 46 | background-color: rgba(249, 237, 190, 0.9); 47 | padding: 5px; 48 | width: 250px; 49 | margin-left: -125px; 50 | text-align: center; 51 | cursor: pointer; 52 | } 53 | 54 | #tabs { 55 | background-color: #f1f1f1; 56 | border-bottom: 1px solid #2d2d2d; 57 | border-top: 1px solid #2d2d2d; 58 | margin: 0; 59 | padding-top: 10px; 60 | padding-bottom: 10px; 61 | } 62 | 63 | #tabs > li { 64 | cursor: pointer; 65 | display: inline; 66 | margin-left: -4px; 67 | border-right: 1px solid #2d2d2d; 68 | padding: 10px 10px 10px 8px; 69 | } 70 | 71 | #tabs > li:first-child { 72 | margin-left: 0; 73 | } 74 | 75 | #tabs > li:hover { 76 | background-color: #d9d9d9; 77 | } 78 | 79 | #tabs > li.selected { 80 | background-color: #d9d9d9; 81 | } 82 | 83 | .panel { 84 | margin: 5px; 85 | } 86 | 87 | label { 88 | display: block; 89 | } 90 | 91 | form > div { 92 | margin-bottom: 10px; 93 | } 94 | 95 | .wide { 96 | width: 300px; 97 | } 98 | 99 | #tos { 100 | font-size: 11px; 101 | color: #a7a7a7; 102 | } 103 | 104 | #tos > a { 105 | color: #a7a7a7; 106 | } 107 | 108 | #existing-videos { 109 | margin-top: 10px; 110 | } 111 | 112 | .admin-video-list > li { 113 | float: left; 114 | } 115 | 116 | .video-container { 117 | background-color: #eeeeee; 118 | border: 1px solid #dddddd; 119 | margin: 5px; 120 | padding: 10px; 121 | width: 356px; 122 | height: 230px; 123 | position: relative; 124 | } 125 | 126 | .video-title { 127 | font-weight: bold; 128 | font-size: 13px; 129 | } 130 | 131 | .video-duration { 132 | margin-left: 5px; 133 | font-size: 11px; 134 | } 135 | 136 | .video-uploaded { 137 | color: #777777; 138 | font-size: 11px; 139 | } 140 | 141 | .thumbnail-image { 142 | cursor: pointer; 143 | position: absolute; 144 | top: 7px; 145 | clip: rect(34px 356px 233px 0); 146 | width: 356px; 147 | height: 267px; 148 | } 149 | 150 | .play-overlay { 151 | cursor: pointer; 152 | display: none; 153 | position: absolute; 154 | top: 89px; 155 | left: 131px; 156 | } 157 | 158 | .video-container input:first-child { 159 | position: absolute; 160 | top: 5px; 161 | right: 5px; 162 | } 163 | 164 | .video-container input { 165 | position: absolute; 166 | top: 5px; 167 | right: 75px; 168 | } 169 | 170 | #description { 171 | height: 60px; 172 | } 173 | 174 | #webcam-widget { 175 | width: 400px; 176 | height: 287px; 177 | } 178 | 179 | #playlists { 180 | width: 400px; 181 | margin-left: 25px; 182 | } 183 | 184 | #playlists li { 185 | cursor: pointer; 186 | padding: 10px; 187 | border-bottom: 1px #dddddd solid; 188 | } 189 | 190 | #playlists li:first-child { 191 | border-top: 1px #dddddd solid; 192 | } 193 | 194 | #playlists li:hover { 195 | background-color: #eeeeee; 196 | } 197 | 198 | #embed-codes-panel > div { 199 | margin-bottom: 15px; 200 | } 201 | 202 | #switch { 203 | clear: left; 204 | margin-left: 5px; 205 | margin-top: 15px; 206 | } 207 | 208 | #moderation-message { 209 | margin: 5px; 210 | } -------------------------------------------------------------------------------- /static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youtube/yt-direct-lite-javascript/b1fac5f3a1e811111c1b06a331b10f987c18e4e6/static/images/favicon.ico -------------------------------------------------------------------------------- /static/images/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youtube/yt-direct-lite-javascript/b1fac5f3a1e811111c1b06a331b10f987c18e4e6/static/images/play.png -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | YouTube Direct Lite 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 19 | 24 | 25 | 28 | 57 | 62 | 65 | 66 | -------------------------------------------------------------------------------- /static/js/admin-main.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | requirejs([ 18 | 'jquery', 19 | 'ytdl/load', 20 | 'ytdl/auth' 21 | ], function($, load, auth) { 22 | auth.initAuth(); 23 | 24 | $(function() { 25 | load.onLoad('admin'); 26 | }); 27 | }); -------------------------------------------------------------------------------- /static/js/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | requirejs([ 18 | 'jquery', 19 | 'ytdl/load', 20 | 'ytdl/auth' 21 | ], function($, load, auth) { 22 | auth.initAuth(); 23 | 24 | $(function() { 25 | load.onLoad('upload'); 26 | }); 27 | }); -------------------------------------------------------------------------------- /static/js/third-party/jquery.parseparams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * $.parseParams - parse query string paramaters into an object. 3 | */ 4 | (function($) { 5 | var re = /([^&=]+)=?([^&]*)/g; 6 | var decodeRE = /\+/g; // Regex for replacing addition symbol with a space 7 | var decode = function (str) { return decodeURIComponent( str.replace(decodeRE, " ") ); }; 8 | $.parseParams = function(query) { 9 | var params = {}, e; 10 | while ( e = re.exec(query) ) params[ decode(e[1]) ] = decode( e[2] ); 11 | return params; 12 | }; 13 | })(jQuery); -------------------------------------------------------------------------------- /static/js/third-party/lscache.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lscache library 3 | * Copyright (c) 2011, Pamela Fox 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /*jshint undef:true, browser:true */ 19 | 20 | /** 21 | * Creates a namespace for the lscache functions. 22 | */ 23 | var lscache = function() { 24 | 25 | // Prefix for all lscache keys 26 | var CACHE_PREFIX = 'lscache-'; 27 | 28 | // Suffix for the key name on the expiration items in localStorage 29 | var CACHE_SUFFIX = '-cacheexpiration'; 30 | 31 | // expiration date radix (set to Base-36 for most space savings) 32 | var EXPIRY_RADIX = 10; 33 | 34 | // time resolution in minutes 35 | var EXPIRY_UNITS = 60 * 1000; 36 | 37 | // ECMAScript max Date (epoch + 1e8 days) 38 | var MAX_DATE = Math.floor(8.64e15/EXPIRY_UNITS); 39 | 40 | var cachedStorage; 41 | var cachedJSON; 42 | 43 | // Determines if localStorage is supported in the browser; 44 | // result is cached for better performance instead of being run each time. 45 | // Feature detection is based on how Modernizr does it; 46 | // it's not straightforward due to FF4 issues. 47 | // It's not run at parse-time as it takes 200ms in Android. 48 | function supportsStorage() { 49 | var key = '__lscachetest__'; 50 | var value = key; 51 | 52 | if (cachedStorage !== undefined) { 53 | return cachedStorage; 54 | } 55 | 56 | try { 57 | setItem(key, value); 58 | removeItem(key); 59 | cachedStorage = true; 60 | } catch (exc) { 61 | cachedStorage = false; 62 | } 63 | return cachedStorage; 64 | } 65 | 66 | // Determines if native JSON (de-)serialization is supported in the browser. 67 | function supportsJSON() { 68 | /*jshint eqnull:true */ 69 | if (cachedJSON === undefined) { 70 | cachedJSON = (window.JSON != null); 71 | } 72 | return cachedJSON; 73 | } 74 | 75 | /** 76 | * Returns the full string for the localStorage expiration item. 77 | * @param {String} key 78 | * @return {string} 79 | */ 80 | function expirationKey(key) { 81 | return key + CACHE_SUFFIX; 82 | } 83 | 84 | /** 85 | * Returns the number of minutes since the epoch. 86 | * @return {number} 87 | */ 88 | function currentTime() { 89 | return Math.floor((new Date().getTime())/EXPIRY_UNITS); 90 | } 91 | 92 | /** 93 | * Wrapper functions for localStorage methods 94 | */ 95 | 96 | function getItem(key) { 97 | return localStorage.getItem(CACHE_PREFIX + key); 98 | } 99 | 100 | function setItem(key, value) { 101 | // Fix for iPad issue - sometimes throws QUOTA_EXCEEDED_ERR on setItem. 102 | localStorage.removeItem(CACHE_PREFIX + key); 103 | localStorage.setItem(CACHE_PREFIX + key, value); 104 | } 105 | 106 | function removeItem(key) { 107 | localStorage.removeItem(CACHE_PREFIX + key); 108 | } 109 | 110 | return { 111 | 112 | /** 113 | * Stores the value in localStorage. Expires after specified number of minutes. 114 | * @param {string} key 115 | * @param {Object|string} value 116 | * @param {number} time 117 | */ 118 | set: function(key, value, time) { 119 | if (!supportsStorage()) return; 120 | 121 | // If we don't get a string value, try to stringify 122 | // In future, localStorage may properly support storing non-strings 123 | // and this can be removed. 124 | if (typeof value !== 'string') { 125 | if (!supportsJSON()) return; 126 | try { 127 | value = JSON.stringify(value); 128 | } catch (e) { 129 | // Sometimes we can't stringify due to circular refs 130 | // in complex objects, so we won't bother storing then. 131 | return; 132 | } 133 | } 134 | 135 | try { 136 | setItem(key, value); 137 | } catch (e) { 138 | if (e.name === 'QUOTA_EXCEEDED_ERR' || e.name === 'NS_ERROR_DOM_QUOTA_REACHED') { 139 | // If we exceeded the quota, then we will sort 140 | // by the expire time, and then remove the N oldest 141 | var storedKeys = []; 142 | var storedKey; 143 | for (var i = 0; i < localStorage.length; i++) { 144 | storedKey = localStorage.key(i); 145 | 146 | if (storedKey.indexOf(CACHE_PREFIX) === 0 && storedKey.indexOf(CACHE_SUFFIX) < 0) { 147 | var mainKey = storedKey.substr(CACHE_PREFIX.length); 148 | var exprKey = expirationKey(mainKey); 149 | var expiration = getItem(exprKey); 150 | if (expiration) { 151 | expiration = parseInt(expiration, EXPIRY_RADIX); 152 | } else { 153 | // TODO: Store date added for non-expiring items for smarter removal 154 | expiration = MAX_DATE; 155 | } 156 | storedKeys.push({ 157 | key: mainKey, 158 | size: (getItem(mainKey)||'').length, 159 | expiration: expiration 160 | }); 161 | } 162 | } 163 | // Sorts the keys with oldest expiration time last 164 | storedKeys.sort(function(a, b) { return (b.expiration-a.expiration); }); 165 | 166 | var targetSize = (value||'').length; 167 | while (storedKeys.length && targetSize > 0) { 168 | storedKey = storedKeys.pop(); 169 | removeItem(storedKey.key); 170 | removeItem(expirationKey(storedKey.key)); 171 | targetSize -= storedKey.size; 172 | } 173 | try { 174 | setItem(key, value); 175 | } catch (e) { 176 | // value may be larger than total quota 177 | return; 178 | } 179 | } else { 180 | // If it was some other error, just give up. 181 | return; 182 | } 183 | } 184 | 185 | // If a time is specified, store expiration info in localStorage 186 | if (time) { 187 | setItem(expirationKey(key), (currentTime() + time).toString(EXPIRY_RADIX)); 188 | } else { 189 | // In case they previously set a time, remove that info from localStorage. 190 | removeItem(expirationKey(key)); 191 | } 192 | }, 193 | 194 | /** 195 | * Retrieves specified value from localStorage, if not expired. 196 | * @param {string} key 197 | * @return {string|Object} 198 | */ 199 | get: function(key) { 200 | if (!supportsStorage()) return null; 201 | 202 | // Return the de-serialized item if not expired 203 | var exprKey = expirationKey(key); 204 | var expr = getItem(exprKey); 205 | 206 | if (expr) { 207 | var expirationTime = parseInt(expr, EXPIRY_RADIX); 208 | 209 | // Check if we should actually kick item out of storage 210 | if (currentTime() >= expirationTime) { 211 | removeItem(key); 212 | removeItem(exprKey); 213 | return null; 214 | } 215 | } 216 | 217 | // Tries to de-serialize stored value if its an object, and returns the normal value otherwise. 218 | var value = getItem(key); 219 | if (!value || !supportsJSON()) { 220 | return value; 221 | } 222 | 223 | try { 224 | // We can't tell if its JSON or a string, so we try to parse 225 | return JSON.parse(value); 226 | } catch (e) { 227 | // If we can't parse, it's probably because it isn't an object 228 | return value; 229 | } 230 | }, 231 | 232 | /** 233 | * Removes a value from localStorage. 234 | * Equivalent to 'delete' in memcache, but that's a keyword in JS. 235 | * @param {string} key 236 | */ 237 | remove: function(key) { 238 | if (!supportsStorage()) return null; 239 | removeItem(key); 240 | removeItem(expirationKey(key)); 241 | }, 242 | 243 | /** 244 | * Returns whether local storage is supported. 245 | * Currently exposed for testing purposes. 246 | * @return {boolean} 247 | */ 248 | supported: function() { 249 | return supportsStorage(); 250 | }, 251 | 252 | /** 253 | * Flushes all lscache items and expiry markers without affecting rest of localStorage 254 | */ 255 | flush: function() { 256 | if (!supportsStorage()) return; 257 | 258 | // Loop in reverse as removing items will change indices of tail 259 | for (var i = localStorage.length-1; i >= 0 ; --i) { 260 | var key = localStorage.key(i); 261 | if (key.indexOf(CACHE_PREFIX) === 0) { 262 | localStorage.removeItem(key); 263 | } 264 | } 265 | } 266 | }; 267 | }(); 268 | -------------------------------------------------------------------------------- /static/js/third-party/parseUri.js: -------------------------------------------------------------------------------- 1 | // parseUri 1.2.2 2 | // (c) Steven Levithan 3 | // MIT License 4 | 5 | function parseUri (str) { 6 | var o = parseUri.options, 7 | m = o.parser[o.strictMode ? "strict" : "loose"].exec(str), 8 | uri = {}, 9 | i = 14; 10 | 11 | while (i--) uri[o.key[i]] = m[i] || ""; 12 | 13 | uri[o.q.name] = {}; 14 | uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) { 15 | if ($1) uri[o.q.name][$1] = $2; 16 | }); 17 | 18 | return uri; 19 | }; 20 | 21 | parseUri.options = { 22 | strictMode: false, 23 | key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], 24 | q: { 25 | name: "queryKey", 26 | parser: /(?:^|&)([^&=]*)=?([^&]*)/g 27 | }, 28 | parser: { 29 | strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, 30 | loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /static/js/ytdl/auth.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define(['jquery', './utils', './constants', './config', './globals'], function($, utils, constants, config, globals) { 18 | var auth = { 19 | initAuth: function() { 20 | window[constants.CLIENT_LIB_LOAD_CALLBACK] = function() { 21 | gapi.auth.init(function() { 22 | if (lscache.get(constants.DISPLAY_NAME_CACHE_KEY)) { 23 | window.setTimeout(function() { 24 | gapi.auth.authorize({ 25 | client_id: config.OAUTH2_CLIENT_ID, 26 | scope: [constants.OAUTH2_SCOPE], 27 | immediate: true 28 | }, auth.onAuthResult); 29 | }, 1); 30 | } else { 31 | utils.redirect('login'); 32 | } 33 | }); 34 | }; 35 | 36 | $.getScript(constants.CLIENT_LIB_URL + constants.CLIENT_LIB_LOAD_CALLBACK); 37 | }, 38 | 39 | onAuthResult: function(authResult) { 40 | if (authResult) { 41 | gapi.client.load(constants.YOUTUBE_API_SERVICE_NAME, constants.YOUTUBE_API_VERSION, auth.onYouTubeClientLoad); 42 | } else { 43 | lscache.flush(); 44 | utils.redirect('login'); 45 | } 46 | }, 47 | 48 | onYouTubeClientLoad: function() { 49 | var nextState = globals.hashParams.state || ''; 50 | if (nextState == 'login') { 51 | nextState = ''; 52 | } 53 | 54 | if (lscache.get(constants.DISPLAY_NAME_CACHE_KEY)) { 55 | utils.redirect(nextState); 56 | } else { 57 | var request = gapi.client[constants.YOUTUBE_API_SERVICE_NAME].channels.list({ 58 | mine: true, 59 | part: 'snippet,contentDetails,status' 60 | }); 61 | request.execute(function(response) { 62 | if (utils.itemsInResponse(response)) { 63 | if (response.items[0].status.isLinked) { 64 | lscache.set(constants.UPLOADS_LIST_ID_CACHE_KEY, response.items[0].contentDetails.relatedPlaylists.uploads); 65 | lscache.set(constants.DISPLAY_NAME_CACHE_KEY, response.items[0].snippet.title); 66 | lscache.set(constants.PROFILE_PICTURE_CACHE_KEY, response.items[0].snippet.thumbnails.default.url); 67 | utils.redirect(nextState); 68 | } else { 69 | utils.showHtmlMessage('Your account cannot upload videos. Please visit https://www.youtube.com/signin?next=/create_channel to add a YouTube channel to your account, and try again.'); 70 | } 71 | } else { 72 | utils.showMessage('Unable to retrieve channel info. ' + utils.getErrorResponseString(response)); 73 | } 74 | }); 75 | } 76 | } 77 | }; 78 | 79 | return auth; 80 | }); -------------------------------------------------------------------------------- /static/js/ytdl/config.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define({ 18 | // REQUIRED 19 | // See https://developers.google.com/api-client-library/javascript/features/authentication 20 | // for instructions on registering for OAuth 2. 21 | // After generating your OAuth 2 client id, you MUST then visit the "Services" tab of 22 | // https://code.google.com/apis/console/ find the entry for "YouTube Data API v3" 23 | // and flip it to "ON". 24 | OAUTH2_CLIENT_ID: '', 25 | 26 | // REQUIRED 27 | // Register at https://code.google.com/apis/youtube/dashboard/gwt/index.html to get your own key. 28 | DEVELOPER_KEY: '' 29 | 30 | // If you'd like to enable Google Analytics statistics to your YouTube Direct Lite instance, 31 | // register for a Google Analytics account and enter your id code below. 32 | //,GOOGLE_ANALYTICS_ID: 'UA-########-#' 33 | 34 | // Setting any or all of these three fields are optional. 35 | // If set then the value(s) will be used for new video uploads, and your users won't be prompted for the corresponding fields on the video upload form. 36 | //,VIDEO_TITLE: 'Video Submission' 37 | //,VIDEO_DESCRIPTION: 'This is a video submission.' 38 | // Make sure that this corresponds to an assignable category! 39 | // See https://developers.google.com/youtube/2.0/reference#YouTube_Category_List 40 | //,VIDEO_CATEGORY: 'People' 41 | }); -------------------------------------------------------------------------------- /static/js/ytdl/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define({ 18 | CATEGORIES_CACHE_EXPIRATION_MINUTES: 3 * 24 * 60, 19 | CATEGORIES_CACHE_KEY: 'categories', 20 | DISPLAY_NAME_CACHE_KEY: 'display_name', 21 | UPLOADS_LIST_ID_CACHE_KEY: 'uploads_list_id', 22 | PROFILE_PICTURE_CACHE_KEY: 'profile_picture', 23 | GENERIC_PROFILE_PICTURE_URL: '//s.ytimg.com/yt/img/no_videos_140-vfl1fDI7-.png', 24 | OAUTH2_TOKEN_TYPE: 'Bearer', 25 | OAUTH2_SCOPE: 'https://gdata.youtube.com', 26 | GDATA_SERVER: 'https://gdata.youtube.com', 27 | CLIENT_LIB_LOAD_CALLBACK: 'onClientLibReady', 28 | CLIENT_LIB_URL: 'https://apis.google.com/js/client.js?onload=', 29 | YOUTUBE_API_SERVICE_NAME: 'youtube', 30 | YOUTUBE_API_VERSION: 'v3', 31 | PAGE_SIZE: 50, 32 | MAX_ITEMS_TO_RETRIEVE: 200, 33 | FEED_CACHE_MINUTES: 5, 34 | STATE_CACHE_MINUTES: 15, 35 | MAX_KEYWORD_LENGTH: 30, 36 | KEYWORD_UPDATE_XML_TEMPLATE: ' {0} ', 37 | WIDGET_EMBED_CODE: '', 38 | PLAYLIST_EMBED_CODE: '', 39 | SUBMISSION_RSS_FEED: 'https://gdata.youtube.com/feeds/api/videos?v=2&alt=rss&orderby=published&category=%7Bhttp%3A%2F%2Fgdata.youtube.com%2Fschemas%2F2007%2Fkeywords.cat%7D{0}', 40 | DEFAULT_KEYWORD: 'ytdl', 41 | WEBCAM_VIDEO_TITLE: 'Webcam Submission', 42 | WEBCAM_VIDEO_DESCRIPTION: 'Uploaded via a webcam.', 43 | REJECTED_VIDEOS_PLAYLIST: 'Rejected YTDL Submissions', 44 | NO_THUMBNAIL_URL: '//i.ytimg.com/vi/hqdefault.jpg', 45 | VIDEO_CONTAINER_TEMPLATE: '
  • {title}({duration})
    Uploaded on {uploadedDate}
  • ', 46 | VIDEO_LI_TEMPLATE: '
  • {3}({5})
    Uploaded on {4}
  • ', 47 | ADMIN_VIDEO_LI_TEMPLATE: '
  • {buttonsHtml}
    {title}({duration})
    Uploaded on {uploadedDate} by {uploader}
  • ', 48 | PLAYLIST_LI_TEMPLATE: '
  • {playlistName}
  • ' 49 | }); -------------------------------------------------------------------------------- /static/js/ytdl/globals.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define({ 18 | parsedUrl: parseUri(window.location.href), 19 | rejectedPlaylistId: '' 20 | }); -------------------------------------------------------------------------------- /static/js/ytdl/load.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define([ 18 | 'jquery', 19 | './constants', 20 | './globals', 21 | './utils', 22 | './config', 23 | './player', 24 | 'ytdl/panels/upload', 25 | 'ytdl/panels/existing', 26 | 'ytdl/panels/login', 27 | 'ytdl/panels/logout', 28 | 'ytdl/panels/postupload', 29 | 'ytdl/panels/select', 30 | 'ytdl/panels/webcam', 31 | 'ytdl/panels/admin', 32 | 'ytdl/panels/embed-codes', 33 | 'ytdl/panels/pending', 34 | 'ytdl/panels/approved', 35 | 'ytdl/panels/rejected' 36 | ], function($, constants, globals, utils, config, player, upload) { 37 | return { 38 | onLoad: function(defaultTab) { 39 | window._gaq = window._gaq || []; 40 | 41 | if (config.GOOGLE_ANALYTICS_ID) { 42 | window._gaq.push(['_setAccount', config.GOOGLE_ANALYTICS_ID]); 43 | 44 | var analyticsScriptSrc = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; 45 | $.getScript(analyticsScriptSrc); 46 | } 47 | 48 | if (!config.OAUTH2_CLIENT_ID || !config.DEVELOPER_KEY) { 49 | utils.showHtmlMessage('YouTube Direct Lite isn\'t configured. Please see the setup guide for more info.'); 50 | return; 51 | } 52 | 53 | if (!$.support.cors) { 54 | if (defaultTab == 'admin') { 55 | utils.showHtmlMessage('Unfortunately, your browser is not supported. Please try visiting this page using a recent version of Firefox, Safari, Opera, or Chrome.'); 56 | window._gaq.push(['_trackPageview']); 57 | return; 58 | } else { 59 | defaultTab = 'existing'; 60 | delete upload.init; 61 | delete upload.display; 62 | $('#upload-panel').html('

    Your browser does not meet the requirements needed to upload a new video. Please choose one of the other options.

    '); 63 | } 64 | } 65 | 66 | utils.updateHashParams(); 67 | 68 | $('body').on({ 69 | click: function() { 70 | utils.redirect($(this).attr('data-state'), $(this).attr('data-playlist-id')); 71 | } 72 | }, '[data-state]'); 73 | 74 | $('#message').click(function() { 75 | $(this).hide(); 76 | }); 77 | 78 | $('body').on({ 79 | click: function() { 80 | player.playVideo(this, $(this).data('video-id')); 81 | }, 82 | mouseenter: function() { 83 | $(this).find('.play-overlay').fadeIn(); 84 | }, 85 | mouseleave: function() { 86 | $(this).find('.play-overlay').fadeOut(); 87 | } 88 | }, '.thumbnail-container'); 89 | 90 | $(window).bind('hashchange', function() { 91 | utils.updateHashParams(); 92 | var state = globals.hashParams.state || defaultTab; 93 | 94 | $('#profile-picture').attr('src', lscache.get(constants.PROFILE_PICTURE_CACHE_KEY) || constants.GENERIC_PROFILE_PICTURE_URL); 95 | var displayName = lscache.get(constants.DISPLAY_NAME_CACHE_KEY); 96 | $('#display-name').text(displayName); 97 | if (displayName) { 98 | $('.login-required').show(); 99 | } else { 100 | $('.login-required').hide(); 101 | } 102 | 103 | var panel; 104 | try { 105 | panel = require(utils.format('ytdl/panels/{0}', state)); 106 | } catch (e) { 107 | utils.showMessage(utils.format('Unknown panel: {0}', state)); 108 | return; 109 | } 110 | 111 | utils.hideMessage(); 112 | 113 | if (panel.needsPlaylist && !globals.hashParams.playlist) { 114 | utils.showMessage('The "playlist" URL parameter is missing.'); 115 | return; 116 | } 117 | 118 | if ('init' in panel) { 119 | panel.init(); 120 | delete panel.init; 121 | } 122 | 123 | if ('display' in panel) { 124 | panel.display(); 125 | } 126 | 127 | $('.panel').hide(); 128 | $(utils.format('#{0}-panel', state)).show(); 129 | $('#tabs > li').removeClass('selected'); 130 | $(utils.format('#tabs > li[data-state={0}]', state)).addClass('selected'); 131 | 132 | window._gaq.push(['_trackPageview', utils.format('{0}#state={1}', location.pathname, state)]); 133 | }); 134 | } 135 | }; 136 | }); -------------------------------------------------------------------------------- /static/js/ytdl/panels/admin.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define(['jquery', '../utils', '../constants'], function($, utils, constants) { 18 | var admin = { 19 | init: function() { 20 | admin.attachClickHandler(); 21 | }, 22 | 23 | display: function() { 24 | $('#tabs').hide(); 25 | $('#switch').hide(); 26 | $('#moderation-message').hide(); 27 | 28 | utils.getPlaylists(function(playlists) { 29 | var lis = []; 30 | $.each(playlists, function() { 31 | lis.push(utils.format(constants.PLAYLIST_LI_TEMPLATE, { 32 | playlistId: this.id, 33 | playlistName: this.snippet.title 34 | })); 35 | }); 36 | $('#playlists').html(lis.sort().join('')); 37 | $('#playlists').append('
  • '); 38 | 39 | admin.attachClickHandler(); 40 | }); 41 | }, 42 | 43 | // TODO: This is a bit of a hack to work around the fact that the #create-playlist button is 44 | // recreated each time display() is called. There's obviously better ways of dealing with that. 45 | attachClickHandler: function() { 46 | $('#create-playlist').click(function() { 47 | $(this).attr('disabled', true); 48 | var playlistName = $('#new-playlist-name').val(); 49 | if (playlistName) { 50 | utils.addPlaylist(playlistName, false, function(playlistId) { 51 | utils.redirect('embed-codes', playlistId); 52 | }); 53 | } else { 54 | utils.showMessage('Please enter a name for the playlist.'); 55 | } 56 | 57 | $(this).removeAttr('disabled'); 58 | }); 59 | } 60 | }; 61 | 62 | return admin; 63 | }); -------------------------------------------------------------------------------- /static/js/ytdl/panels/approved.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define(['jquery', '../utils', '../constants', '../globals'], function($, utils, constants, globals) { 18 | return { 19 | needsPlaylist: true, 20 | init: function() { 21 | $('body').on({ 22 | click: function() { 23 | var button = $(this); 24 | button.attr('disabled', true); 25 | utils.addVideoToPlaylist(globals.rejectedPlaylistId, button.attr('data-video-id')); 26 | utils.removeVideoFromPlaylist(button.attr('data-edit-url')); 27 | 28 | window._gaq.push(['_trackEvent', 'Admin', 'Reject']); 29 | 30 | utils.animateModeration($(this).closest('li'), $('li[data-state=rejected]')); 31 | } 32 | }, '#approved-panel input.reject'); 33 | }, 34 | display: function() { 35 | $('#tabs').show(); 36 | $('#switch').show(); 37 | 38 | utils.getPlaylists(function() { 39 | utils.getStateOfSubmissions(function(state) { 40 | if (state) { 41 | var lis = []; 42 | 43 | $.each(state.approvedIds, function() { 44 | var metadata = state.videoIdToMetadata[this]; 45 | if (metadata) { 46 | var editUrl = state.videoIdToPlaylistEntryId[this]; 47 | metadata.buttonsHtml = utils.format('', { 48 | videoId: this, 49 | editUrl: editUrl 50 | }); 51 | 52 | lis.push(utils.format(constants.ADMIN_VIDEO_LI_TEMPLATE, metadata)); 53 | } 54 | }); 55 | 56 | $('#approved-videos').html(lis.join('')); 57 | 58 | $('#moderation-message').text(utils.format('{0} {1} approved.', lis.length, lis.length == 1 ? 'video is' : 'videos are')).show(); 59 | } else { 60 | utils.showMessage(utils.format('Unable to determine submission state. Is the "{0}" playlist missing?', constants.REJECTED_VIDEOS_PLAYLIST)); 61 | } 62 | }); 63 | }); 64 | } 65 | }; 66 | }); -------------------------------------------------------------------------------- /static/js/ytdl/panels/embed-codes.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define(['jquery', '../utils', '../constants', '../globals'], function($, utils, constants, globals) { 18 | return { 19 | needsPlaylist: true, 20 | display: function() { 21 | $('#tabs').show(); 22 | $('#switch').show(); 23 | $('#moderation-message').hide(); 24 | 25 | $('#widget-embed-code').text(utils.format(constants.WIDGET_EMBED_CODE, utils.currentUrlWithoutParams().replace('admin.html', 'index.html'), globals.hashParams.playlist)); 26 | $('#playlist-embed-code').text(utils.format(constants.PLAYLIST_EMBED_CODE, globals.hashParams.playlist)); 27 | $('#rss-feed').attr('href', utils.format(constants.SUBMISSION_RSS_FEED, utils.generateKeywordFromPlaylistId(globals.hashParams.playlist))); 28 | } 29 | }; 30 | }); -------------------------------------------------------------------------------- /static/js/ytdl/panels/existing.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define(['jquery', '../utils', '../constants', '../globals'], function($, utils, constants, globals) { 18 | return { 19 | needsPlaylist: true, 20 | init: function() { 21 | $('#existing-panel').on({ 22 | click: function() { 23 | $(this).attr('disabled', true); 24 | var video = $(this).data('video'); 25 | 26 | if (!('tags' in video.snippet)) { 27 | video.snippet.tags = []; 28 | } 29 | 30 | if ($.inArray(constants.DEFAULT_KEYWORD, video.snippet.tags) == -1) { 31 | video.snippet.tags.push(constants.DEFAULT_KEYWORD); 32 | } 33 | 34 | var keyword = utils.generateKeywordFromPlaylistId(globals.hashParams.playlist); 35 | if ($.inArray(keyword, video.snippet.tags) == -1) { 36 | video.snippet.tags.push(keyword); 37 | } 38 | 39 | delete video.contentDetails; 40 | delete video.status; 41 | 42 | var request = gapi.client[constants.YOUTUBE_API_SERVICE_NAME].videos.update({ 43 | part: 'snippet', 44 | resource: video 45 | }); 46 | request.execute(function(response) { 47 | if ('error' in response) { 48 | utils.showMessage('Submission failed. ' + utils.getErrorResponseString(response)); 49 | $(this).removeAttr('disabled'); 50 | window._gaq.push(['_trackEvent', 'Submission', 'Existing', 'Error']); 51 | } else { 52 | utils.showMessage('Your submission was received.'); 53 | window._gaq.push(['_trackEvent', 'Submission', 'Existing', 'Success']); 54 | } 55 | }); 56 | } 57 | }, '.submit-video-button'); 58 | 59 | utils.getAllItems('playlistItems', { 60 | part: 'snippet', 61 | playlistId: lscache.get(constants.UPLOADS_LIST_ID_CACHE_KEY) 62 | }, function(items) { 63 | var videoIds = $.map(items, function(item) { 64 | return item.snippet.resourceId.videoId; 65 | }); 66 | utils.getInfoForVideoIds(videoIds, function(videos) { 67 | videos.sort(function(a, b) { 68 | return a.snippet.publishedAt > b.snippet.publishedAt ? -1 : 1; 69 | }); 70 | 71 | $.each(videos, function() { 72 | if (this.status.uploadStatus != 'processed' || this.status.privacyStatus != 'public') { 73 | return true; 74 | } 75 | 76 | var videoLi = $(utils.format(constants.VIDEO_CONTAINER_TEMPLATE, { 77 | thumbnailUrl: this.snippet.thumbnails.high.url, 78 | uploadedDate: new Date(this.snippet.publishedAt).toDateString(), 79 | duration: utils.formatPeriodOfTime(this.contentDetails.duration), 80 | title: this.snippet.title, 81 | videoId: this.id 82 | })); 83 | videoLi.find('.submit-video-button').data('video', this); 84 | videoLi.appendTo('#existing-videos'); 85 | }); 86 | }); 87 | }); 88 | } 89 | }; 90 | }); -------------------------------------------------------------------------------- /static/js/ytdl/panels/login.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define(['jquery', '../auth', '../constants', '../config'], function($, auth, constants, config) { 18 | return { 19 | init: function() { 20 | $('#login').click(function() { 21 | gapi.auth.authorize({ 22 | client_id: config.OAUTH2_CLIENT_ID, 23 | scope: [constants.OAUTH2_SCOPE], 24 | immediate: false 25 | }, auth.onAuthResult); 26 | }); 27 | } 28 | }; 29 | }); -------------------------------------------------------------------------------- /static/js/ytdl/panels/logout.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define(['../utils'], function(utils) { 18 | return { 19 | display: function() { 20 | lscache.flush(); 21 | utils.redirect('login'); 22 | } 23 | }; 24 | }); -------------------------------------------------------------------------------- /static/js/ytdl/panels/pending.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define(['jquery', '../utils', '../constants', '../globals'], function($, utils, constants, globals) { 18 | return { 19 | needsPlaylist: true, 20 | init: function() { 21 | $('body').on({ 22 | click: function() { 23 | $(this).attr('disabled', true); 24 | utils.addVideoToPlaylist(globals.hashParams.playlist, $(this).attr('data-video-id')); 25 | 26 | window._gaq.push(['_trackEvent', 'Admin', 'Approve']); 27 | 28 | utils.animateModeration($(this).closest('li'), $('li[data-state=approved]')); 29 | } 30 | }, '#pending-panel input.approve'); 31 | 32 | $('body').on({ 33 | click: function() { 34 | $(this).attr('disabled', true); 35 | utils.addVideoToPlaylist(globals.rejectedPlaylistId, $(this).attr('data-video-id')); 36 | 37 | window._gaq.push(['_trackEvent', 'Admin', 'Reject']); 38 | 39 | utils.animateModeration($(this).closest('li'), $('li[data-state=rejected]')); 40 | } 41 | }, '#pending-panel input.reject'); 42 | }, 43 | display: function() { 44 | $('#tabs').show(); 45 | $('#switch').show(); 46 | 47 | utils.getPlaylists(function() { 48 | utils.getStateOfSubmissions(function(state) { 49 | if (state) { 50 | var lis = []; 51 | 52 | $.each(state.pendingIds, function() { 53 | var metadata = state.videoIdToMetadata[this]; 54 | if (metadata) { 55 | metadata.buttonsHtml = utils.format('', this); 56 | 57 | lis.push(utils.format(constants.ADMIN_VIDEO_LI_TEMPLATE, metadata)); 58 | } 59 | }); 60 | 61 | $('#pending-videos').html(lis.join('')); 62 | 63 | $('#moderation-message').text(utils.format('{0} {1} pending moderation.', lis.length, lis.length == 1 ? 'video is' : 'videos are')).show(); 64 | } else { 65 | utils.showMessage(utils.format('Unable to determine submission state. Is the "{0}" playlist missing?', constants.REJECTED_VIDEOS_PLAYLIST)); 66 | } 67 | }); 68 | }); 69 | } 70 | }; 71 | }); -------------------------------------------------------------------------------- /static/js/ytdl/panels/postupload.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define(['jquery', '../utils', '../globals'], function($, utils, globals) { 18 | return { 19 | init: function() { 20 | var status = globals.parsedUrl.queryKey.status; 21 | if (status == 200) { 22 | var youtubeUrl = utils.format('http://youtu.be/{0}', globals.parsedUrl.queryKey.id); 23 | $('#youtube-link').attr('href', youtubeUrl); 24 | $('#upload-success').show(); 25 | 26 | window._gaq.push(['_trackEvent', 'Submission', 'Upload', 'Success']); 27 | } else { 28 | $('#upload-success').hide(); 29 | utils.showMessage(utils.format('Your video could not be submitted. (Error: {0})', globals.parsedUrl.queryKey.code)); 30 | 31 | window._gaq.push(['_trackEvent', 'Submission', 'Upload', 'Error']); 32 | } 33 | } 34 | }; 35 | }); -------------------------------------------------------------------------------- /static/js/ytdl/panels/rejected.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define(['jquery', '../utils', '../constants', '../globals'], function($, utils, constants, globals) { 18 | return { 19 | needsPlaylist: true, 20 | init: function () { 21 | $('body').on({ 22 | click: function() { 23 | var button = $(this); 24 | button.attr('disabled', true); 25 | utils.addVideoToPlaylist(globals.hashParams.playlist, button.attr('data-video-id')); 26 | utils.removeVideoFromPlaylist(button.attr('data-edit-url')); 27 | 28 | window._gaq.push(['_trackEvent', 'Admin', 'Approve']); 29 | 30 | utils.animateModeration($(this).closest('li'), $('li[data-state=approved]')); 31 | } 32 | }, '#rejected-panel input.approve'); 33 | }, 34 | display: function() { 35 | $('#tabs').show(); 36 | $('#switch').show(); 37 | 38 | utils.getPlaylists(function() { 39 | utils.getStateOfSubmissions(function(state) { 40 | if (state) { 41 | var lis = []; 42 | 43 | $.each(state.rejectedIds, function() { 44 | var metadata = state.videoIdToMetadata[this]; 45 | if (metadata) { 46 | var editUrl = state.videoIdToPlaylistEntryId[this]; 47 | metadata.buttonsHtml = utils.format('', { 48 | videoId: this, 49 | editUrl: editUrl 50 | }); 51 | 52 | lis.push(utils.format(constants.ADMIN_VIDEO_LI_TEMPLATE, metadata)); 53 | } 54 | }); 55 | 56 | $('#rejected-videos').html(lis.join('')); 57 | 58 | $('#moderation-message').text(utils.format('{0} {1} rejected.', lis.length, lis.length == 1 ? 'video is' : 'videos are')).show(); 59 | } else { 60 | utils.showMessage(utils.format('Unable to determine submission state. Is the "{0}" playlist missing?', constants.REJECTED_VIDEOS_PLAYLIST)); 61 | } 62 | }); 63 | }); 64 | } 65 | }; 66 | }); -------------------------------------------------------------------------------- /static/js/ytdl/panels/select.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define({ 18 | needsPlaylist: true 19 | }); -------------------------------------------------------------------------------- /static/js/ytdl/panels/upload.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define(['jquery', '../utils', '../constants', '../globals', '../config'], function($, utils, constants, globals, config) { 18 | return { 19 | needsPlaylist: true, 20 | init: function() { 21 | function populateCategoriesSelect(xml) { 22 | var assignableCategories = xml.find('atom\\:category:has(yt\\:assignable), category:has(assignable)'); 23 | var options = []; 24 | $.each(assignableCategories, function() { 25 | var term = $(this).attr('term'); 26 | var label = $(this).attr('label'); 27 | options.push(utils.format('', label, term)); 28 | }); 29 | $('#category').html(options.sort().join('')); 30 | 31 | } 32 | 33 | if (!config.VIDEO_CATEGORY) { 34 | $('#category-container').show(); 35 | var categoriesXml = lscache.get(constants.CATEGORIES_CACHE_KEY); 36 | if (categoriesXml) { 37 | populateCategoriesSelect($(new DOMParser().parseFromString(categoriesXml, 'text/xml'))); 38 | } else { 39 | $.ajax({ 40 | dataType: 'xml', 41 | type: 'GET', 42 | url: utils.format('{0}/schemas/2007/categories.cat', constants.GDATA_SERVER), 43 | headers: utils.generateYouTubeApiHeaders(), 44 | success: function(responseXml) { 45 | var xmlString = new XMLSerializer().serializeToString(responseXml); 46 | lscache.set(constants.CATEGORIES_CACHE_KEY, xmlString, constants.CATEGORIES_CACHE_EXPIRATION_MINUTES); 47 | populateCategoriesSelect($(responseXml)); 48 | }, 49 | error: function(jqXHR) { 50 | utils.showMessage('The list of categories could not be loaded: ' + jqXHR.responseText); 51 | } 52 | }); 53 | } 54 | } 55 | 56 | if (!config.VIDEO_TITLE) { 57 | $('#title-container').show(); 58 | } 59 | if (!config.VIDEO_DESCRIPTION) { 60 | $('#description-container').show(); 61 | } 62 | 63 | $('#upload').click(function() { 64 | $('#upload').attr('disabled', true); 65 | $('#upload').val('Uploading...'); 66 | 67 | var keywordList = [ constants.DEFAULT_KEYWORD ]; 68 | if (globals.hashParams.playlist) { 69 | var playlistId = globals.hashParams.playlist; 70 | var keyword = utils.generateKeywordFromPlaylistId(playlistId); 71 | keywordList.push(keyword); 72 | } 73 | 74 | var jsonBody = { 75 | data: { 76 | title: config.VIDEO_TITLE || $('#title').val(), 77 | category: config.VIDEO_CATEGORY || $('#category option:selected').val(), 78 | description: config.VIDEO_DESCRIPTION || $('#description').val(), 79 | tags: keywordList 80 | } 81 | } 82 | 83 | $.ajax({ 84 | dataType: 'xml', 85 | type: 'POST', 86 | url: utils.format('{0}/action/GetUploadToken', constants.GDATA_SERVER), 87 | headers: utils.generateYouTubeApiHeaders(), 88 | data: JSON.stringify(jsonBody), 89 | contentType: 'application/json', 90 | processData: false, 91 | success: function(responseXml) { 92 | utils.hideMessage(); 93 | var xml = $(responseXml); 94 | var nextUrl = utils.format('{0}#state=postupload&playlist={1}', utils.currentUrlWithoutParams(), globals.hashParams.playlist); 95 | var submissionUrl = utils.format('{0}?nexturl={1}', xml.find('url').text(), encodeURIComponent(nextUrl)); 96 | var token = xml.find('token').text(); 97 | 98 | $('#upload-form').attr('action', submissionUrl); 99 | $('').attr({ 100 | type: 'hidden', 101 | name: 'token', 102 | value: token 103 | }).appendTo('#upload-form'); 104 | 105 | $('#upload-form').submit(); 106 | }, 107 | error: function(jqXHR) { 108 | utils.showMessage('Metadata submission failed: ' + jqXHR.responseText); 109 | $('#upload').removeAttr('disabled'); 110 | $('#upload').val('Upload'); 111 | } 112 | }); 113 | }); 114 | 115 | $('[required]').change(function() { 116 | var disabled = false; 117 | 118 | $.each($('[required]:visible'), function() { 119 | if (!$(this).val()) { 120 | disabled = true; 121 | } 122 | }); 123 | 124 | if (disabled) { 125 | $('#upload').attr('disabled', true); 126 | } else { 127 | $('#upload').removeAttr('disabled'); 128 | } 129 | }); 130 | } 131 | }; 132 | }); -------------------------------------------------------------------------------- /static/js/ytdl/panels/webcam.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define(['jquery', '../utils', '../constants', '../globals', '../config'], function($, utils, constants, globals, config) { 18 | var webcam = { 19 | needsPlaylist: true, 20 | init: function() { 21 | if (typeof(YT) == 'undefined' || typeof(YT.UploadWidget) == 'undefined') { 22 | window.onYouTubeIframeAPIReady = function() { 23 | webcam.loadUploadWidget(); 24 | }; 25 | 26 | $.getScript('//www.youtube.com/iframe_api'); 27 | } else { 28 | webcam.loadUploadWidget(); 29 | } 30 | }, 31 | 32 | loadUploadWidget: function() { 33 | new YT.UploadWidget('webcam-widget', { 34 | webcamOnly: true, 35 | events: { 36 | onApiReady: function(event) { 37 | event.target.setVideoTitle(config.VIDEO_TITLE || constants.WEBCAM_VIDEO_TITLE); 38 | event.target.setVideoDescription(config.VIDEO_DESCRIPTION || constants.WEBCAM_VIDEO_DESCRIPTION); 39 | event.target.setVideoKeywords([utils.generateKeywordFromPlaylistId(globals.hashParams.playlist)]); 40 | }, 41 | onUploadSuccess: function(event) { 42 | utils.showMessage('Your webcam submission was received.'); 43 | 44 | window._gaq.push(['_trackEvent', 'Submission', 'Webcam', 'Success']); 45 | }, 46 | onStateChange: function(event) { 47 | if (event.data.state == YT.UploadWidgetState.ERROR) { 48 | window._gaq.push(['_trackEvent', 'Submission', 'Webcam', 'Error']); 49 | } 50 | } 51 | } 52 | }); 53 | } 54 | }; 55 | 56 | return webcam; 57 | }); -------------------------------------------------------------------------------- /static/js/ytdl/player.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define(['jquery'], function($) { 18 | var player = { 19 | playVideo: function(container, videoId) { 20 | if (typeof(YT) == 'undefined' || typeof(YT.Player) == 'undefined') { 21 | window.onYouTubeIframeAPIReady = function() { 22 | player.loadPlayer(container, videoId); 23 | }; 24 | 25 | $.getScript('//www.youtube.com/iframe_api'); 26 | } else { 27 | player.loadPlayer(container, videoId); 28 | } 29 | }, 30 | 31 | loadPlayer: function(container, videoId) { 32 | new YT.Player(container, { 33 | videoId: videoId, 34 | width: 356, 35 | height: 200, 36 | playerVars: { 37 | autoplay: 1, 38 | controls: 0, 39 | modestbranding: 1, 40 | rel: 0, 41 | showInfo: 0 42 | } 43 | }); 44 | } 45 | }; 46 | 47 | return player; 48 | }); -------------------------------------------------------------------------------- /static/js/ytdl/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2012 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | define([ 18 | 'jquery', 19 | './constants', 20 | './globals', 21 | './config', 22 | 'third-party/jquery.parseparams' 23 | ], function($, constants, globals, config) { 24 | var utils = { 25 | format: function() { 26 | var replacements = Array.prototype.slice.call(arguments); 27 | var originalString = replacements.shift(); 28 | if ($.isPlainObject(replacements[0])) { 29 | replacements = replacements[0]; 30 | } 31 | if (originalString) { 32 | return originalString.replace(/{(\w+)}/g, function(match, i) { 33 | return typeof replacements[i] != 'undefined' ? replacements[i] : ''; 34 | }); 35 | } else { 36 | return ''; 37 | } 38 | }, 39 | 40 | generateYouTubeApiHeaders: function(extraHeaders) { 41 | var headers = extraHeaders || {}; 42 | headers['Authorization'] = utils.format('{0} {1}', constants.OAUTH2_TOKEN_TYPE, gapi.auth.getToken().access_token); 43 | headers['GData-Version'] = 2; 44 | headers['X-GData-Key'] = utils.format('key={0}', config.DEVELOPER_KEY); 45 | headers['X-GData-Client'] = utils.format('{0}-{1}', constants.DEFAULT_KEYWORD, globals.parsedUrl.authority); 46 | return headers; 47 | }, 48 | 49 | escapeXmlEntities: function(input) { 50 | if (input) { 51 | return input.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); 52 | } else { 53 | return ''; 54 | } 55 | }, 56 | 57 | showMessage: function(message) { 58 | $('#message').text(message); 59 | $('#message').show(); 60 | }, 61 | 62 | showHtmlMessage: function(message) { 63 | $('#message').html(message); 64 | $('#message').show(); 65 | }, 66 | 67 | hideMessage: function() { 68 | $('#message').hide(); 69 | $('#message').text(); 70 | }, 71 | 72 | animateModeration: function(target, destination) { 73 | var currentPosition = target.position(); 74 | var destinationPosition = destination.position(); 75 | var originalHeight = target.height(); 76 | var originalWidth = target.width(); 77 | 78 | target.css({ 79 | position: 'absolute', 80 | left: currentPosition.left, 81 | top: currentPosition.top 82 | }); 83 | 84 | target.animate({ 85 | opacity: 0.5, 86 | left: destinationPosition.left, 87 | top: destinationPosition.top, 88 | height: originalHeight * 0.1, 89 | width: originalWidth * 0.1 90 | }, 'slow', function() { 91 | target.hide(); 92 | }); 93 | }, 94 | 95 | updateHashParams: function() { 96 | globals.hashParams = $.parseParams(decodeURIComponent(window.location.hash.replace('#', ''))); 97 | }, 98 | 99 | generateKeywordFromPlaylistId: function(playlistId) { 100 | playlistId = playlistId || ''; 101 | 102 | if (playlistId.indexOf('PL') == 0) { 103 | playlistId = playlistId.substring(2); 104 | } 105 | 106 | var keyword = utils.format('{0}{1}', constants.DEFAULT_KEYWORD, playlistId); 107 | keyword = keyword.replace(/\W/g, ''); 108 | if (keyword.length > constants.MAX_KEYWORD_LENGTH) { 109 | keyword = keyword.substring(0, constants.MAX_KEYWORD_LENGTH); 110 | } 111 | return keyword; 112 | }, 113 | 114 | formatDuration: function(durationInSeconds) { 115 | var minutes = parseInt(durationInSeconds / 60); 116 | var seconds = durationInSeconds - (minutes * 60); 117 | if (seconds < 10) { 118 | seconds = utils.format('0{0}', seconds); 119 | } 120 | return utils.format('{0}:{1}', minutes, seconds); 121 | }, 122 | 123 | redirect: function(panel, playlist) { 124 | var hash = utils.format('state={0}&playlist={1}', panel, playlist || globals.hashParams.playlist); 125 | window.location.hash = hash; 126 | if (panel == globals.hashParams.state) { 127 | $(window).trigger('hashchange'); 128 | } 129 | }, 130 | 131 | getFeed: function(options) { 132 | var startIndex = options.startIndex || 1; 133 | var maxResults = options.maxResults || 1000; 134 | var results = options.results || []; 135 | 136 | var url = options.url; 137 | if (url.indexOf('?') == -1) { 138 | url += '?'; 139 | } else { 140 | url += '&'; 141 | } 142 | url = utils.format('{0}max-results={1}&alt=json&start-index={2}', url, constants.PAGE_SIZE, startIndex); 143 | 144 | if (options.cacheMinutes && results.length == 0) { 145 | results = lscache.get(options.url); 146 | if (results == null) { 147 | results = []; 148 | } else { 149 | (options.callback)(results); 150 | return; 151 | } 152 | } 153 | 154 | $.ajax({ 155 | dataType: 'json', 156 | type: 'GET', 157 | url: url, 158 | headers: utils.generateYouTubeApiHeaders(), 159 | success: function(responseJson) { 160 | if ('entry' in responseJson['feed']) { 161 | $.merge(results, responseJson['feed']['entry']); 162 | 163 | if (results.length < maxResults && responseJson['feed']['entry'].length == constants.PAGE_SIZE) { 164 | utils.getFeed({ 165 | cacheMinutes: options.cacheMinutes, 166 | callback: options.callback, 167 | maxResults: maxResults, 168 | results: results, 169 | startIndex: startIndex + constants.PAGE_SIZE, 170 | url: options.url 171 | }); 172 | } else { 173 | if (options.cacheMinutes) { 174 | lscache.set(options.url, results, options.cacheMinutes); 175 | } 176 | (options.callback)(results); 177 | } 178 | } else { 179 | if (options.cacheMinutes) { 180 | lscache.set(options.url, results, options.cacheMinutes); 181 | } 182 | (options.callback)(results); 183 | } 184 | }, 185 | error: function(jqXHR) { 186 | if (results.length > 0) { 187 | (options.callback)(results); 188 | } else { 189 | utils.showMessage('Your request could not be completed: ' + jqXHR.responseText); 190 | } 191 | } 192 | }); 193 | }, 194 | 195 | getThumbnailUrlFromEntry: function(entry, thumbnailName) { 196 | var thumbnailUrl = constants.NO_THUMBNAIL_URL; 197 | 198 | if ('media$group' in entry && 'media$thumbnail' in entry['media$group']) { 199 | $.each(entry['media$group']['media$thumbnail'], function(i, thumbnailEntry) { 200 | if (thumbnailEntry['yt$name'] == thumbnailName) { 201 | thumbnailUrl = thumbnailEntry['url']; 202 | } 203 | }); 204 | } 205 | 206 | return thumbnailUrl; 207 | }, 208 | 209 | currentUrlWithoutParams: function() { 210 | return utils.format('{0}://{1}{2}', globals.parsedUrl.protocol, globals.parsedUrl.authority, globals.parsedUrl.path); 211 | }, 212 | 213 | itemsInResponse: function(response) { 214 | return ('items' in response && response.items.length > 0); 215 | }, 216 | 217 | getErrorResponseString: function(response) { 218 | var errorString = 'Unknown error.'; 219 | 220 | if ('error' in response && 'data' in response.error && response.error.data.length > 0) { 221 | var error = response.error.data[0]; 222 | errorString = utils.format('{0}: {1}', error.reason || 'Error', error.message); 223 | if (error.location) { 224 | errorString += utils.format(' ({0})', error.location); 225 | } 226 | } 227 | 228 | return errorString; 229 | }, 230 | 231 | getAllItems: function(service, params, callback, items) { 232 | if (!items) { 233 | items = []; 234 | } 235 | params['maxResults'] = constants.PAGE_SIZE; 236 | 237 | var request = gapi.client.request({ 238 | path: utils.format('/{0}/{1}/{2}', constants.YOUTUBE_API_SERVICE_NAME, constants.YOUTUBE_API_VERSION, service), 239 | method: 'GET', 240 | params: params 241 | }); 242 | request.execute(function(response) { 243 | if (utils.itemsInResponse(response)) { 244 | items = items.concat(response.items); 245 | } else if ('error' in response) { 246 | utils.showMessage('Request failed. ' + utils.getErrorResponseString(response)); 247 | } 248 | 249 | if ('nextPageToken' in response && items.length < constants.MAX_ITEMS_TO_RETRIEVE) { 250 | params['pageToken'] = response.nextPageToken; 251 | utils.getAllItems(service, params, callback, items); 252 | } else { 253 | callback(items); 254 | } 255 | }); 256 | }, 257 | 258 | getInfoForVideoIds: function(videoIds, callback, videos) { 259 | if (!videos) { 260 | videos = []; 261 | } 262 | 263 | var pageOfVideoIds = videoIds.splice(0, constants.PAGE_SIZE); 264 | if (pageOfVideoIds.length > 0) { 265 | var request = gapi.client[constants.YOUTUBE_API_SERVICE_NAME].videos.list({ 266 | id: pageOfVideoIds.join(','), 267 | part: 'snippet,contentDetails,status' 268 | }); 269 | request.execute(function(response) { 270 | if (utils.itemsInResponse(response)) { 271 | videos = videos.concat(response.items); 272 | utils.getInfoForVideoIds(videoIds, callback, videos); 273 | } else { 274 | utils.showMessage('Unable to retrieve info about videos. ' + utils.getErrorResponseString(response)); 275 | callback(videos); 276 | } 277 | }); 278 | } else { 279 | callback(videos); 280 | } 281 | }, 282 | 283 | formatPeriodOfTime: function(duration) { 284 | var matches = duration.match(/PT(?:(\d+)H)?(?:(\d+)M)?(\d+)S/); 285 | 286 | var hours = parseInt(matches[1]) || 0; 287 | var minutes = parseInt(matches[2]) || 0; 288 | minutes += hours * 60; 289 | var seconds = parseInt(matches[3]) || 0; 290 | if (seconds < 10) { 291 | seconds = '0' + seconds; 292 | } 293 | 294 | return utils.format('{0}:{1}', minutes, seconds); 295 | }, 296 | 297 | getEditLinkUrlFromEntry: function(entry) { 298 | var editLinkUrl = ''; 299 | $.each(entry['link'], function(i, link) { 300 | if (link['rel'] == 'edit') { 301 | editLinkUrl = link['href']; 302 | } 303 | }); 304 | return editLinkUrl; 305 | }, 306 | 307 | getPlaylistIdFromEditUrl: function(editUrl) { 308 | var matches = /playlists\/([^/]+)\//.exec(editUrl); 309 | if (matches.length > 1) { 310 | return matches[1]; 311 | } 312 | 313 | return ''; 314 | }, 315 | 316 | getPlaylists: function(callback) { 317 | utils.getAllItems('playlists', { 318 | part: 'id,snippet', 319 | mine: true 320 | }, function(playlists) { 321 | var entriesToReturn = []; 322 | $.each(playlists, function() { 323 | if (this.snippet.title == constants.REJECTED_VIDEOS_PLAYLIST) { 324 | globals.rejectedPlaylistId = this.id; 325 | } else { 326 | entriesToReturn.push(this); 327 | } 328 | }); 329 | 330 | if (globals.rejectedPlaylistId) { 331 | callback(entriesToReturn); 332 | } else { 333 | utils.addPlaylist(constants.REJECTED_VIDEOS_PLAYLIST, true, function(playlistId) { 334 | globals.rejectedPlaylistId = playlistId; 335 | 336 | callback(entriesToReturn); 337 | }); 338 | } 339 | }); 340 | }, 341 | 342 | addPlaylist: function(name, isPrivate, callback) { 343 | lscache.remove(utils.format('{0}/feeds/api/users/default/playlists', constants.GDATA_SERVER)); 344 | 345 | var request = gapi.client.youtube.playlists.insert({ 346 | part: 'snippet,status', 347 | resource: { 348 | snippet: { 349 | title: name 350 | }, 351 | status: { 352 | privacyStatus: isPrivate ? 'private' : 'public' 353 | } 354 | } 355 | }); 356 | request.execute(function(response) { 357 | if ('error' in response) { 358 | utils.showMessage('Could not create playlist. ' + utils.getErrorResponseString(response)); 359 | } else { 360 | callback(response.id); 361 | } 362 | }); 363 | 364 | window._gaq.push(['_trackEvent', 'Admin', 'Create Playlist']); 365 | }, 366 | 367 | getMetadataFromEntry: function(entry) { 368 | var durationInSeconds = 0; 369 | if ('yt$duration' in entry['media$group']) { 370 | durationInSeconds = entry['media$group']['yt$duration']['seconds']; 371 | } 372 | 373 | return { 374 | thumbnailUrl: utils.getThumbnailUrlFromEntry(entry, 'hqdefault'), 375 | uploadedDate: new Date(entry['published']['$t']).toDateString(), 376 | duration: utils.formatDuration(durationInSeconds), 377 | uploader: entry['media$group']['media$credit'][0]['yt$display'], 378 | videoId: entry['media$group']['yt$videoid']['$t'], 379 | title: entry['title']['$t'] 380 | }; 381 | }, 382 | 383 | addVideoToPlaylist: function(playlistId, videoId) { 384 | lscache.remove(utils.format('{0}/feeds/api/users/default/playlists', constants.GDATA_SERVER)); 385 | lscache.remove(playlistId); 386 | 387 | var request = gapi.client.youtube.playlistItems.insert({ 388 | part: 'snippet', 389 | resource: { 390 | snippet: { 391 | playlistId: playlistId, 392 | resourceId: { 393 | kind: 'youtube#video', 394 | videoId: videoId 395 | }, 396 | position: 0 397 | } 398 | } 399 | }); 400 | request.execute(function(response) { 401 | if ('error' in response) { 402 | utils.showMessage('Could not add video playlist. ' + utils.getErrorResponseString(response)); 403 | } else { 404 | utils.showMessage('Success!'); 405 | } 406 | }); 407 | }, 408 | 409 | removeVideoFromPlaylist: function(editUrl) { 410 | var playlistId = utils.getPlaylistIdFromEditUrl(editUrl); 411 | if (playlistId) { 412 | lscache.remove(playlistId); 413 | } 414 | lscache.remove(utils.format('{0}/feeds/api/users/default/playlists', constants.GDATA_SERVER)); 415 | 416 | $.ajax({ 417 | type: 'DELETE', 418 | url: editUrl, 419 | headers: utils.generateYouTubeApiHeaders(), 420 | success: function() { 421 | utils.showMessage('Success!'); 422 | }, 423 | error: function(jqXHR) { 424 | utils.showMessage(utils.format('Could not remove video from playlist: {0}', jqXHR.responseText)); 425 | } 426 | }); 427 | }, 428 | 429 | getStateOfSubmissions: function(callback) { 430 | var state = lscache.get(globals.hashParams.playlist); 431 | if (state) { 432 | callback(state); 433 | return; 434 | } 435 | 436 | if (!globals.rejectedPlaylistId) { 437 | callback(); 438 | return; 439 | } 440 | 441 | utils.getFeed({ 442 | url: utils.format('{0}/feeds/api/playlists/{1}', constants.GDATA_SERVER, globals.hashParams.playlist), 443 | callback: function(playlistEntries) { 444 | var videoIdToPlaylistEntryId = {}; 445 | var videoIdToMetadata = {}; 446 | var approvedIds = []; 447 | 448 | $.each(playlistEntries, function() { 449 | var approvedId = this['media$group']['yt$videoid']['$t']; 450 | approvedIds.push(approvedId); 451 | videoIdToPlaylistEntryId[approvedId] = utils.getEditLinkUrlFromEntry(this); 452 | videoIdToMetadata[approvedId] = utils.getMetadataFromEntry(this); 453 | }); 454 | 455 | utils.getFeed({ 456 | url: utils.format('{0}/feeds/api/playlists/{1}', constants.GDATA_SERVER, globals.rejectedPlaylistId), 457 | callback: function(rejectedPlaylistEntries) { 458 | var allRejectedIds = []; 459 | var rejectedIds = []; 460 | 461 | $.each(rejectedPlaylistEntries, function() { 462 | var rejectedId = this['media$group']['yt$videoid']['$t']; 463 | if (!(rejectedId in videoIdToPlaylistEntryId)) { 464 | allRejectedIds.push(rejectedId); 465 | videoIdToPlaylistEntryId[rejectedId] = utils.getEditLinkUrlFromEntry(this); 466 | videoIdToMetadata[rejectedId] = utils.getMetadataFromEntry(this); 467 | } 468 | }); 469 | 470 | var keyword = utils.generateKeywordFromPlaylistId(globals.hashParams.playlist); 471 | utils.getFeed({ 472 | url: utils.format('{0}/feeds/api/videos?category=%7Bhttp%3A%2F%2Fgdata.youtube.com%2Fschemas%2F2007%2Fkeywords.cat%7D{1}', constants.GDATA_SERVER, keyword), 473 | callback: function(searchEntries) { 474 | var pendingIds = []; 475 | $.each(searchEntries, function() { 476 | var searchId = this['media$group']['yt$videoid']['$t']; 477 | if (!(searchId in videoIdToPlaylistEntryId)) { 478 | pendingIds.push(searchId); 479 | videoIdToMetadata[searchId] = utils.getMetadataFromEntry(this); 480 | } else if ($.inArray(searchId, allRejectedIds) != -1) { 481 | rejectedIds.push(searchId); 482 | } 483 | }); 484 | 485 | state = { 486 | videoIdToPlaylistEntryId: videoIdToPlaylistEntryId, 487 | videoIdToMetadata: videoIdToMetadata, 488 | pendingIds: pendingIds, 489 | approvedIds: approvedIds, 490 | rejectedIds: rejectedIds 491 | }; 492 | 493 | lscache.set(globals.hashParams.playlist, state, constants.STATE_CACHE_MINUTES); 494 | callback(state); 495 | } 496 | }); 497 | } 498 | }); 499 | } 500 | }); 501 | } 502 | }; 503 | 504 | return utils; 505 | }); --------------------------------------------------------------------------------