├── .DS_Store ├── .github ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ └── pylint.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── db.sqlite3 ├── manage.py ├── playlist_project ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── songs ├── .DS_Store ├── __init__.py ├── admin.py ├── apple_music.py ├── apps.py ├── client_secret.json ├── command.py ├── exceptions.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_all_playlist_apple_music_all_playlist_spotify.py │ └── __init__.py ├── models.py ├── spotify.py ├── tests.py ├── urls.py └── views.py └── templates ├── all_apple_music.html ├── all_apple_music_playlist-results-partial.html ├── all_apple_music_playlist_search.html ├── all_spotify.html ├── all_spotify_playlist-results-partial.html ├── all_spotify_playlist_search.html ├── apple_music.html ├── base.html ├── index.html ├── new_apple_music.html ├── new_spotify.html ├── registration ├── change-password.html └── login.html └── spotify.html /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rohan-cod/Music_Playlist/b40f3ecdbcd7409d90a0829fbbdbb91a01c1404c/.DS_Store -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: 9 | - cron: '29 16 * * 3' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ 'python' ] 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v2 27 | 28 | - name: Initialize CodeQL 29 | uses: github/codeql-action/init@v1 30 | with: 31 | languages: ${{ matrix.language }} 32 | - name: Autobuild 33 | uses: github/codeql-action/autobuild@v1 34 | 35 | 36 | - name: Perform CodeQL Analysis 37 | uses: github/codeql-action/analyze@v1 38 | -------------------------------------------------------------------------------- /.github/workflows/pylint.yml: -------------------------------------------------------------------------------- 1 | name: Pylint 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up Python 3.9 13 | uses: actions/setup-python@v2 14 | with: 15 | python-version: 3.9 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install pylint 20 | - name: Analysing the code with pylint 21 | run: | 22 | pylint `ls -R|grep .py$|xargs` 23 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at rohaninjmu@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Rohan Gupta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | django = "==3.2.5" 10 | google-auth-oauthlib = "*" 11 | google-api-python-client = "*" 12 | youtube-dl = "*" 13 | exception = "*" 14 | schedule = "*" 15 | gunicorn = "==20.1.0" 16 | 17 | [requires] 18 | python_version = "3.7" 19 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "561b0935b30b67c79a6f5758d253fd39b0dd7c22eb03ea763e9b790ab6e98741" 5 | "sha256": "8dcc88141bb339f5efbd801ce7371c87a4f8150fb280e49db0dbef0d0dc10494" 6 | }, 7 | "pipfile-spec": 6, 8 | "requires": { 9 | "python_version": "3.7" 10 | }, 11 | "sources": [ 12 | { 13 | "name": "pypi", 14 | "url": "https://pypi.org/simple", 15 | "verify_ssl": true 16 | } 17 | ] 18 | }, 19 | "default": { 20 | "argparse": { 21 | "hashes": [ 22 | "sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4", 23 | "sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314" 24 | ], 25 | "version": "==1.4.0" 26 | }, 27 | "asgiref": { 28 | "hashes": [ 29 | "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9", 30 | "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214" 31 | ], 32 | "markers": "python_version >= '3.6'", 33 | "version": "==3.4.1" 34 | }, 35 | "cachetools": { 36 | "hashes": [ 37 | "sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001", 38 | "sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff" 39 | ], 40 | "markers": "python_version ~= '3.5'", 41 | "version": "==4.2.2" 42 | }, 43 | "certifi": { 44 | "hashes": [ 45 | "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", 46 | "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8" 47 | ], 48 | "version": "==2021.5.30" 49 | }, 50 | "chardet": { 51 | "hashes": [ 52 | "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", 53 | "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" 54 | ], 55 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 56 | "version": "==4.0.0" 57 | }, 58 | "django": { 59 | "hashes": [ 60 | "sha256:3da05fea54fdec2315b54a563d5b59f3b4e2b1e69c3a5841dda35019c01855cd", 61 | "sha256:c58b5f19c5ae0afe6d75cbdd7df561e6eb929339985dbbda2565e1cabb19a62e" 62 | ], 63 | "index": "pypi", 64 | "version": "==3.2.5" 65 | }, 66 | "exception": { 67 | "hashes": [ 68 | "sha256:b11176dc5c4386a1649476d498b2efd3cd6eb1151ca6c2214f16b90a785c0a3b", 69 | "sha256:f8ce0ed8e75fafa9f948e37548290cb8d188a154a17426b1a9ca01f39647cf08" 70 | ], 71 | "index": "pypi", 72 | "version": "==0.1.0" 73 | }, 74 | "google-api-core": { 75 | "hashes": [ 76 | "sha256:7c8ba88e2b893ef4f67d67e229aade51a2db5053023b73b1394a5ee3dcdb561c", 77 | "sha256:a9f979b5c6a9cad31a4120ca6be712df76adc32336a7bf4bc2f4331a1b527fe7" 78 | ], 79 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 80 | "version": "==1.31.0" 81 | }, 82 | "google-api-python-client": { 83 | "hashes": [ 84 | "sha256:345bb7249ac9af93a6c75e4adb93a41139459945aee61e611a5c8e5b146cbef7", 85 | "sha256:b3874333cabc5ce46dcda2da48f1b258e227a9ae61688211e277e11270386307" 86 | ], 87 | "index": "pypi", 88 | "version": "==2.13.0" 89 | }, 90 | "google-auth": { 91 | "hashes": [ 92 | "sha256:9266252e11393943410354cf14a77bcca24dd2ccd9c4e1aef23034fe0fbae630", 93 | "sha256:c7c215c74348ef24faef2f7b62f6d8e6b38824fe08b1e7b7b09a02d397eda7b3" 94 | ], 95 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 96 | "version": "==1.32.1" 97 | }, 98 | "google-auth-httplib2": { 99 | "hashes": [ 100 | "sha256:31e49c36c6b5643b57e82617cb3e021e3e1d2df9da63af67252c02fa9c1f4a10", 101 | "sha256:a07c39fd632becacd3f07718dfd6021bf396978f03ad3ce4321d060015cc30ac" 102 | ], 103 | "version": "==0.1.0" 104 | }, 105 | "google-auth-oauthlib": { 106 | "hashes": [ 107 | "sha256:09832c6e75032f93818edf1affe4746121d640c625a5bef9b5c96af676e98eee", 108 | "sha256:0e92aacacfb94978de3b7972cf4b0f204c3cd206f74ddd0dc0b31e91164e6317" 109 | ], 110 | "index": "pypi", 111 | "version": "==0.4.4" 112 | }, 113 | "googleapis-common-protos": { 114 | "hashes": [ 115 | "sha256:a88ee8903aa0a81f6c3cec2d5cf62d3c8aa67c06439b0496b49048fb1854ebf4", 116 | "sha256:f6d561ab8fb16b30020b940e2dd01cd80082f4762fa9f3ee670f4419b4b8dbd0" 117 | ], 118 | "markers": "python_version >= '3.6'", 119 | "version": "==1.53.0" 120 | }, 121 | "gunicorn": { 122 | "hashes": [ 123 | "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", 124 | "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" 125 | ], 126 | "index": "pypi", 127 | "version": "==20.1.0" 128 | }, 129 | "httplib2": { 130 | "hashes": [ 131 | "sha256:0b12617eeca7433d4c396a100eaecfa4b08ee99aa881e6df6e257a7aad5d533d", 132 | "sha256:2ad195faf9faf079723f6714926e9a9061f694d07724b846658ce08d40f522b4" 133 | ], 134 | "version": "==0.19.1" 135 | }, 136 | "idna": { 137 | "hashes": [ 138 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", 139 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" 140 | ], 141 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 142 | "version": "==2.10" 143 | }, 144 | "oauthlib": { 145 | "hashes": [ 146 | "sha256:42bf6354c2ed8c6acb54d971fce6f88193d97297e18602a3a886603f9d7730cc", 147 | "sha256:8f0215fcc533dd8dd1bee6f4c412d4f0cd7297307d43ac61666389e3bc3198a3" 148 | ], 149 | "markers": "python_version >= '3.6'", 150 | "version": "==3.1.1" 151 | }, 152 | "packaging": { 153 | "hashes": [ 154 | "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7", 155 | "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14" 156 | ], 157 | "markers": "python_version >= '3.6'", 158 | "version": "==21.0" 159 | }, 160 | "protobuf": { 161 | "hashes": [ 162 | "sha256:13ee7be3c2d9a5d2b42a1030976f760f28755fcf5863c55b1460fd205e6cd637", 163 | "sha256:145ce0af55c4259ca74993ddab3479c78af064002ec8227beb3d944405123c71", 164 | "sha256:14c1c9377a7ffbeaccd4722ab0aa900091f52b516ad89c4b0c3bb0a4af903ba5", 165 | "sha256:1556a1049ccec58c7855a78d27e5c6e70e95103b32de9142bae0576e9200a1b0", 166 | "sha256:26010f693b675ff5a1d0e1bdb17689b8b716a18709113288fead438703d45539", 167 | "sha256:2ae692bb6d1992afb6b74348e7bb648a75bb0d3565a3f5eea5bec8f62bd06d87", 168 | "sha256:2bfb815216a9cd9faec52b16fd2bfa68437a44b67c56bee59bc3926522ecb04e", 169 | "sha256:4ffbd23640bb7403574f7aff8368e2aeb2ec9a5c6306580be48ac59a6bac8bde", 170 | "sha256:59e5cf6b737c3a376932fbfb869043415f7c16a0cf176ab30a5bbc419cd709c1", 171 | "sha256:6902a1e4b7a319ec611a7345ff81b6b004b36b0d2196ce7a748b3493da3d226d", 172 | "sha256:6ce4d8bf0321e7b2d4395e253f8002a1a5ffbcfd7bcc0a6ba46712c07d47d0b4", 173 | "sha256:6d847c59963c03fd7a0cd7c488cadfa10cda4fff34d8bc8cba92935a91b7a037", 174 | "sha256:72804ea5eaa9c22a090d2803813e280fb273b62d5ae497aaf3553d141c4fdd7b", 175 | "sha256:7a4c97961e9e5b03a56f9a6c82742ed55375c4a25f2692b625d4087d02ed31b9", 176 | "sha256:85d6303e4adade2827e43c2b54114d9a6ea547b671cb63fafd5011dc47d0e13d", 177 | "sha256:8727ee027157516e2c311f218ebf2260a18088ffb2d29473e82add217d196b1c", 178 | "sha256:99938f2a2d7ca6563c0ade0c5ca8982264c484fdecf418bd68e880a7ab5730b1", 179 | "sha256:9b7a5c1022e0fa0dbde7fd03682d07d14624ad870ae52054849d8960f04bc764", 180 | "sha256:a22b3a0dbac6544dacbafd4c5f6a29e389a50e3b193e2c70dae6bbf7930f651d", 181 | "sha256:a38bac25f51c93e4be4092c88b2568b9f407c27217d3dd23c7a57fa522a17554", 182 | "sha256:a981222367fb4210a10a929ad5983ae93bd5a050a0824fc35d6371c07b78caf6", 183 | "sha256:ab6bb0e270c6c58e7ff4345b3a803cc59dbee19ddf77a4719c5b635f1d547aa8", 184 | "sha256:c56c050a947186ba51de4f94ab441d7f04fcd44c56df6e922369cc2e1a92d683", 185 | "sha256:e76d9686e088fece2450dbc7ee905f9be904e427341d289acbe9ad00b78ebd47", 186 | "sha256:ebcb546f10069b56dc2e3da35e003a02076aaa377caf8530fe9789570984a8d2", 187 | "sha256:f0e59430ee953184a703a324b8ec52f571c6c4259d496a19d1cabcdc19dabc62", 188 | "sha256:ffea251f5cd3c0b9b43c7a7a912777e0bc86263436a87c2555242a348817221b" 189 | ], 190 | "version": "==3.17.3" 191 | }, 192 | "pyasn1": { 193 | "hashes": [ 194 | "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", 195 | "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", 196 | "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", 197 | "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", 198 | "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", 199 | "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", 200 | "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", 201 | "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", 202 | "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", 203 | "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", 204 | "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", 205 | "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", 206 | "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" 207 | ], 208 | "version": "==0.4.8" 209 | }, 210 | "pyasn1-modules": { 211 | "hashes": [ 212 | "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8", 213 | "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199", 214 | "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811", 215 | "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed", 216 | "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4", 217 | "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", 218 | "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74", 219 | "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb", 220 | "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45", 221 | "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd", 222 | "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0", 223 | "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d", 224 | "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405" 225 | ], 226 | "version": "==0.2.8" 227 | }, 228 | "pyparsing": { 229 | "hashes": [ 230 | "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", 231 | "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" 232 | ], 233 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 234 | "version": "==2.4.7" 235 | }, 236 | "pytz": { 237 | "hashes": [ 238 | "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", 239 | "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" 240 | ], 241 | "version": "==2021.1" 242 | }, 243 | "requests": { 244 | "hashes": [ 245 | "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", 246 | "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" 247 | ], 248 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 249 | "version": "==2.25.1" 250 | }, 251 | "requests-oauthlib": { 252 | "hashes": [ 253 | "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", 254 | "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a", 255 | "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc" 256 | ], 257 | "version": "==1.3.0" 258 | }, 259 | "rsa": { 260 | "hashes": [ 261 | "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2", 262 | "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9" 263 | ], 264 | "markers": "python_version >= '3.6'", 265 | "version": "==4.7.2" 266 | }, 267 | "schedule": { 268 | "hashes": [ 269 | "sha256:617adce8b4bf38c360b781297d59918fbebfb2878f1671d189f4f4af5d0567a4", 270 | "sha256:e6ca13585e62c810e13a08682e0a6a8ad245372e376ba2b8679294f377dfc8e4" 271 | ], 272 | "index": "pypi", 273 | "version": "==1.1.0" 274 | }, 275 | "six": { 276 | "hashes": [ 277 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 278 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 279 | ], 280 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 281 | "version": "==1.16.0" 282 | }, 283 | "sqlparse": { 284 | "hashes": [ 285 | "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0", 286 | "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8" 287 | ], 288 | "markers": "python_version >= '3.5'", 289 | "version": "==0.4.1" 290 | }, 291 | "typing-extensions": { 292 | "hashes": [ 293 | "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", 294 | "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", 295 | "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" 296 | ], 297 | "markers": "python_version < '3.8'", 298 | "version": "==3.10.0.0" 299 | }, 300 | "uritemplate": { 301 | "hashes": [ 302 | "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f", 303 | "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae" 304 | ], 305 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 306 | "version": "==3.0.1" 307 | }, 308 | "urllib3": { 309 | "hashes": [ 310 | "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", 311 | "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" 312 | ], 313 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", 314 | "version": "==1.26.6" 315 | }, 316 | "youtube-dl": { 317 | "hashes": [ 318 | "sha256:263e04d53fb8ba3dfbd246ad09b7d388e896c132a20cc770c26ee7684de050ac", 319 | "sha256:cb2d3ee002158ede783e97a82c95f3817594df54367ea6a77ce5ceea4772f0ab" 320 | ], 321 | "index": "pypi", 322 | "version": "==2021.6.6" 323 | } 324 | }, 325 | "develop": {} 326 | } 327 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Music Playlist App 2 | A Web Application maade using the **Django** framework to automatically add songs liked by you on **YouTube** to a **Spotify** **|** **Apple Music** playlist. 🎧 3 | 4 | ## Configuration Required 5 | 6 | * Go to `songs/spotify.py`. Add your **Spotify Oauth Token** to the `spotify_token` variable and **Spotify user_id** to the `spotify_user_id` variable. To get **user_id** click [here](https://www.spotify.com/us/account/overview/). To get **Oauth Token** click [here](https://developer.spotify.com/console/post-playlists/). 7 | * Go to `songs/apple_music.py`. Add your **Apple music token** to the `apple_music_token` variable. 8 | * Go to `songs/client_secret.json`. Replace the complete contents of the file with the contents of your `client_secret.json` file that you can download after enabling **Oauth for YouTube** from [here](https://developers.google.com/youtube/v3/getting-started/). 9 | 10 | ## Steps to get the Oauth Token from Spotify 11 | * Login to your [Spotify account](https://www.spotify.com/us/account/overview/) and get your username. 12 | * The username will be your Spotify user_id. 13 | * Now, get the [Oauth token](https://developer.spotify.com/console/post-playlists/) by entering your user_id and then click Get Token. 14 | * In the scope options, select the required scopes. 15 | * It will generate a Spotify token and note that this **token expires** every once in a while. So, if you face any issue with accessing your Spotify Playlist, it may be because 16 | your token might have expired. In that case, create a new token by repeating the above process. 17 | 18 | 19 | ## Steps to get the Oauth for Youtube 20 | * Login to the [Google Cloud Platform](https://console.developers.google.com/) 21 | * Create a new project by entering your project name. 22 | * Now enable the the api (**YouTube Data API v3**) by selecting the "ENABLE APIS AND SERVICES" option. 23 | * After enabling the api, crete your credentials by clicking the "Credentials" in the sidebar. 24 | * Now press the "+ CREATE CREDENTIALS" option and then **select the OAuth Client ID** 25 | * Click on "CONFIGURE CONSENT SCREEN" and it redirect to OAuth consent screen. 26 | * Select the External option in the User type and then proceed. 27 | * Now, fill out the required fields and proceed forward. (Note: In the scopes section go through all the options and select the ones you think will be necessary) 28 | * In the Test Users section, add your email address and then save and continue. 29 | * Now, head back to the Credentials section in the sidebar and create the OAuth client ID. 30 | * Select the application type to be "**Web application**". 31 | * Now, in the "Authorized redirect URIs" add your local url of the django app. (It may look like "http://127.0.0.1:8000/") 32 | * After entering all the details, save it and then an OAuth client ID will be created. 33 | * Now download the **json file** by clicking on the download symbol next to your client ID. 34 | * Copy the contents of the json file and paste it inside the client_secret.json file in your projects folder. 35 | 36 | **Note that in the above method, it is mentioned that the app is a web application, so for it work the app needs to deployed first. So, use heroku to deploy the app in the web.** 37 | 38 | ## Requirements 39 | 40 | * Python 3.8 41 | * Django 2.2.10 42 | * [Youtube Data API v3](https://developers.google.com/youtube/v3) 43 | * [Spotify Web API](https://developer.spotify.com/documentation/web-api/) 44 | * [Apple Music API](https://developer.apple.com/documentation/applemusicapi) 45 | * [Requests Library v 2.22.0](https://requests.readthedocs.io/en/master/) 46 | * [Youtube_dl v 2020.01.24](https://github.com/ytdl-org/youtube-dl/) 47 | * Additional requirements are in **Pipfile**. 48 | 49 | ## Setting up the Project 50 | 51 | * Download and install Python 3.7 52 | * Download and install Git. 53 | * Fork the Repository. 54 | * Clone the repository to your local machine `$ git clone https://github.com//Music_Playlist.git` 55 | * Change directory to Music_Playlist `$ cd Music_Playlist` 56 | * Install pipenv `$ pip3 install pipenv` 57 | * Create a virtual environment and install all requirements from Pipfile `$ pipenv install` 58 | * Activate the env: `$ pipenv shell` 59 | * Make migrations `$ python manage.py makemigrations` 60 | * Migrate the changes to the database `$ python manage.py migrate` 61 | * Create superuser `$ python manage.py createsuperuser` 62 | * Run the server `$ python manage.py runserver` 63 | 64 | ## Contributing 65 | 66 | If you are new to open source you can first read about git by [clicking here](https://www.codecademy.com/learn/learn-git). 67 | 68 | ## Communtiy Slack Channel 69 | 70 | To get started, the first step is to meet the community. We use slack to communicate, and there the helpful community will guide you. Slack is an instant messaging service used by developers and users of GitHub. It uses chatrooms, where developers can join in and can talk about a particular topic. [Click here](https://join.slack.com/t/codingninjas-talk/shared_invite/zt-pnokq31t-yZX67OFljSq_TclAKsG_eg) to join our Slack Workspace. 71 | ## Code of Conduct 72 | Check code of conduct [here](https://github.com/shubhdeeprajput/Music_Playlist/blob/master/CODE_OF_CONDUCT.md) 73 | 74 | 75 | ## Communities for extenxive bussiness 76 | Artists 77 | Developers 78 | Advertising 79 | Investors 80 | Vendors 81 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rohan-cod/Music_Playlist/b40f3ecdbcd7409d90a0829fbbdbb91a01c1404c/db.sqlite3 -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'playlist_project.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /playlist_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rohan-cod/Music_Playlist/b40f3ecdbcd7409d90a0829fbbdbb91a01c1404c/playlist_project/__init__.py -------------------------------------------------------------------------------- /playlist_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for playlist_project project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.2.10. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '=qz4sr^9%n2u(yq)nc!gek$mz78r&ay%_r@$k%8@5+i_gfd&jg' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = False 27 | 28 | ALLOWED_HOSTS = ['*'] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'songs.apps.SongsConfig', 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'playlist_project.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [os.path.join(BASE_DIR, "templates")], 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'playlist_project.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 106 | 107 | LANGUAGE_CODE = 'en-us' 108 | 109 | TIME_ZONE = 'Asia/Kolkata' 110 | 111 | USE_I18N = True 112 | 113 | USE_L10N = True 114 | 115 | USE_TZ = True 116 | 117 | 118 | # Static files (CSS, JavaScript, Images) 119 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 120 | 121 | STATIC_URL = '/static/' 122 | LOGIN_URL = 'login' 123 | LOGOUT_URL = 'logout' 124 | LOGIN_REDIRECT_URL = 'home' 125 | LOGOUT_REDIRECT_URL = 'home' 126 | 127 | CRISPY_TEMPLATE_PACK = 'bootstrap4' 128 | -------------------------------------------------------------------------------- /playlist_project/urls.py: -------------------------------------------------------------------------------- 1 | """playlist_project URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url, include 17 | from django.contrib import admin 18 | from django.conf import settings 19 | from django.urls import path, include, reverse_lazy 20 | from django.contrib.auth.views import LoginView 21 | from django.contrib.auth import views as auth_views 22 | from songs import views 23 | 24 | urlpatterns = [ 25 | path( 26 | 'change-password/', 27 | auth_views.PasswordChangeView.as_view( 28 | template_name='registration/change-password.html', 29 | success_url = reverse_lazy('') 30 | ), 31 | name='change_password' 32 | ), 33 | path('admin/', admin.site.urls), 34 | path('users/', include('django.contrib.auth.urls')), 35 | url(r'^login/$',LoginView.as_view(), name='login'), 36 | path('', include('songs.urls')), 37 | 38 | ] 39 | -------------------------------------------------------------------------------- /playlist_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for playlist_project project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'playlist_project.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /songs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rohan-cod/Music_Playlist/b40f3ecdbcd7409d90a0829fbbdbb91a01c1404c/songs/.DS_Store -------------------------------------------------------------------------------- /songs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rohan-cod/Music_Playlist/b40f3ecdbcd7409d90a0829fbbdbb91a01c1404c/songs/__init__.py -------------------------------------------------------------------------------- /songs/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /songs/apple_music.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | import google_auth_oauthlib.flow 5 | import googleapiclient.discovery 6 | import googleapiclient.errors 7 | import requests 8 | import youtube_dl 9 | 10 | apple_music_token = "" 11 | 12 | class A_ToPlaylist(): 13 | def __init__(self): 14 | self.youtube_client = self.get_youtube_client() 15 | self.all_song_info = {} 16 | 17 | def get_youtube_client(self): 18 | os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" 19 | api_service_name = "youtube" 20 | api_version = "v3" 21 | client_secrets_file = "client_secret.json" 22 | scopes = ["https://www.googleapis.com/auth/youtube.readonly"] 23 | flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file( 24 | client_secrets_file, scopes) 25 | credentials = flow.run_console() 26 | youtube_client = googleapiclient.discovery.build( 27 | api_service_name, api_version, credentials=credentials) 28 | 29 | return youtube_client 30 | 31 | def get_liked_videos(self): 32 | request = self.youtube_client.videos().list( 33 | part="snippet,contentDetails,statistics", 34 | myRating="like" 35 | ) 36 | response = request.execute() 37 | for item in response["items"]: 38 | video_title = item["snippet"]["title"] 39 | youtube_url = f'https://www.youtube.com/watch?v={item["id"]}' 40 | video = youtube_dl.YoutubeDL({}).extract_info( 41 | youtube_url, download=False) 42 | song_name = video["track"] 43 | artist = video["artist"] 44 | 45 | if song_name is not None and artist is not None: 46 | self.all_song_info[video_title] = { 47 | "youtube_url": youtube_url, 48 | "song_name": song_name, 49 | "artist": artist, 50 | "apple_music_id": self.get_apple_music_id(song_name) 51 | 52 | } 53 | 54 | def get_playlist(self, playlist_name): 55 | query = f'https://api.music.apple.com/v1/me/library/playlists' 56 | response = requests.get( 57 | query, 58 | headers={ 59 | "Content-Type": "application/json", 60 | "Authorization": f'Bearer {apple_music_token}' 61 | } 62 | ) 63 | response_json = response.json() 64 | for playlist in response_json["data"]: 65 | if playlist["attributes"]["name"] == playlist_name: 66 | result = playlist["id"] 67 | 68 | return result 69 | 70 | def create_playlist(self, playlist_name, description): 71 | request_body = json.dumps({ 72 | "attributes":{ 73 | "name": playlist_name, 74 | "description": description 75 | } 76 | }) 77 | 78 | query = f'https://api.music.apple.com/v1/me/library/playlists' 79 | response = requests.post( 80 | query, 81 | data=request_body, 82 | headers={ 83 | "Content-Type": "application/json", 84 | "Authorization": f'Bearer {apple_music_token}' 85 | } 86 | ) 87 | response_json = response.json() 88 | return response_json["id"] 89 | 90 | def get_all_playlist(self): 91 | query = f'https://api.music.apple.com/v1/me/library/playlists' 92 | response = requests.get( 93 | query, 94 | headers={ 95 | "Content-Type": "application/json", 96 | "Authorization": f'Bearer {apple_music_token}' 97 | } 98 | ) 99 | all_playlists = [] 100 | response_json = response.json() 101 | for playlist in response_json["data"]: 102 | all_playlists.append(playlist["attributes"]["name"]) 103 | 104 | return all_playlists 105 | 106 | def get_apple_music_id(self, song_name): 107 | query = f'https://api.music.apple.com/v1/catalog/in/songs' 108 | response = requests.get( 109 | query, 110 | headers={ 111 | "Content-Type": "application/json", 112 | "Authorization": f'Bearer {apple_music_token}' 113 | } 114 | ) 115 | response_json = response.json() 116 | songs = response_json["data"] 117 | for song in songs: 118 | if song["attributes"]["name"] == song_name: 119 | id_ = song["attributes"]["playparams"]["id"] 120 | 121 | return id_ 122 | 123 | def add_song_to_playlist(self, playlist_name): 124 | self.get_liked_videos() 125 | ids = [info["apple_music_id"] 126 | for song, info in self.all_song_info.items()] 127 | playlist_id = self.get_playlist(playlist_name) 128 | q = f'https://api.music.apple.com/v1/me/library/playlists/{playlist_id}' 129 | res = requests.get( 130 | q, 131 | headers={ 132 | "Content-Type": "application/json", 133 | "Authorization": f'Bearer {apple_music_token}' 134 | }) 135 | res_json = res.json() 136 | tracks = res_json["relationships"]["tracks"] 137 | for track in tracks: 138 | t = track["librarysong"] 139 | _id = t["attributes"]["playparams"]["id"] 140 | if _id in ids: 141 | ids.remove(_id) 142 | 143 | 144 | request_data = json.dumps(ids) 145 | 146 | query = f'https://api.music.apple.com/v1/me/library/playlists/{playlist_id}/tracks' 147 | 148 | response = requests.post( 149 | query, 150 | data=request_data, 151 | headers={ 152 | "Content-Type": "application/json", 153 | "Authorization": f'Bearer {apple_music_token}' 154 | } 155 | ) 156 | 157 | response_json = response.json() 158 | return response_json 159 | 160 | -------------------------------------------------------------------------------- /songs/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SongsConfig(AppConfig): 5 | name = 'songs' 6 | -------------------------------------------------------------------------------- /songs/client_secret.json: -------------------------------------------------------------------------------- 1 | { 2 | "web": { 3 | "client_id": "", 4 | "client_secret": "", 5 | "redirect_uris": [""], 6 | "auth_uri": "https://accounts.google.com/o/oauth2/auth", 7 | "token_uri": "https://accounts.google.com/o/oauth2/token", 8 | "YOUR_API_KEY": "" 9 | } 10 | } -------------------------------------------------------------------------------- /songs/command.py: -------------------------------------------------------------------------------- 1 | import schedule 2 | import time 3 | from .models import All_Playlist_Apple_Music, All_Playlist_Spotify 4 | from .spotify import ToPlaylist 5 | from .apple_music import A_ToPlaylist 6 | 7 | cp = ToPlaylist() 8 | cp_ = A_ToPlaylist() 9 | 10 | 11 | def comm(): 12 | li = cp.get_all_playlist() 13 | for i in li: 14 | All_Playlist_Spotify.objects.update_or_create( 15 | pla_name = i 16 | ) 17 | 18 | li_ = cp.get_all_playlist() 19 | for i_ in li_: 20 | All_Playlist_Apple_Music.objects.update_or_create( 21 | pla_name = i_ 22 | ) 23 | 24 | schedule.every(10).minutes.do(comm) 25 | 26 | while True: 27 | schedule.run_pending() 28 | time.sleep(1) 29 | 30 | 31 | -------------------------------------------------------------------------------- /songs/exceptions.py: -------------------------------------------------------------------------------- 1 | class ResponseException(Exception): 2 | def __init__(self, status_code, message=""): 3 | self.message = message 4 | self.status_code = status_code 5 | 6 | def __str__(self): 7 | return self.message + f"Response gave status code {self.status_code}" -------------------------------------------------------------------------------- /songs/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.10 on 2020-04-10 17:12 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='New_Playlist_Apple_Music', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('playlist_name', models.CharField(max_length=100)), 19 | ('description', models.CharField(max_length=200)), 20 | ], 21 | ), 22 | migrations.CreateModel( 23 | name='New_Playlist_Spotify', 24 | fields=[ 25 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 26 | ('playlist_name', models.CharField(max_length=100)), 27 | ('description', models.CharField(max_length=200)), 28 | ], 29 | ), 30 | migrations.CreateModel( 31 | name='Playlist_Apple_Music', 32 | fields=[ 33 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 34 | ('playlist_name', models.CharField(max_length=100)), 35 | ], 36 | ), 37 | migrations.CreateModel( 38 | name='Playlist_Spotify', 39 | fields=[ 40 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 41 | ('playlist_name', models.CharField(max_length=100)), 42 | ], 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /songs/migrations/0002_all_playlist_apple_music_all_playlist_spotify.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.10 on 2020-04-11 12:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('songs', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='All_Playlist_Apple_Music', 15 | fields=[ 16 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 17 | ('pla_name', models.CharField(max_length=100)), 18 | ], 19 | ), 20 | migrations.CreateModel( 21 | name='All_Playlist_Spotify', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('pla_name', models.CharField(max_length=100)), 25 | ], 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /songs/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rohan-cod/Music_Playlist/b40f3ecdbcd7409d90a0829fbbdbb91a01c1404c/songs/migrations/__init__.py -------------------------------------------------------------------------------- /songs/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.db import models 3 | from django.conf import settings 4 | from django.contrib.auth import get_user_model 5 | from django.urls import reverse 6 | 7 | 8 | class All_Playlist_Apple_Music(models.Model): 9 | pla_name = models.CharField(max_length=100) 10 | 11 | class All_Playlist_Spotify(models.Model): 12 | pla_name = models.CharField(max_length=100) 13 | 14 | class Playlist_Spotify(models.Model): 15 | playlist_name = models.CharField(max_length=100) 16 | 17 | class Playlist_Apple_Music(models.Model): 18 | playlist_name = models.CharField(max_length=100) 19 | 20 | 21 | class New_Playlist_Spotify(models.Model): 22 | 23 | description = models.CharField(max_length=200) 24 | playlist_name = models.CharField(max_length=100) 25 | 26 | class New_Playlist_Apple_Music(models.Model): 27 | 28 | description = models.CharField(max_length=200) 29 | playlist_name = models.CharField(max_length=100) -------------------------------------------------------------------------------- /songs/spotify.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | import google_auth_oauthlib.flow 5 | import googleapiclient.discovery 6 | import googleapiclient.errors 7 | import requests 8 | import youtube_dl 9 | from .exceptions import ResponseException 10 | 11 | spotify_token = "" 12 | spotify_user_id = "" 13 | 14 | class ToPlaylist(): 15 | def __init__(self): 16 | self.youtube_client = self.get_youtube_client() 17 | self.all_song_info = {} 18 | 19 | def get_youtube_client(self): 20 | os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" 21 | 22 | api_service_name = "youtube" 23 | api_version = "v3" 24 | client_secrets_file = "client_secret.json" 25 | scopes = ["https://www.googleapis.com/auth/youtube.readonly"] 26 | flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_secrets_file( 27 | client_secrets_file, scopes) 28 | credentials = flow.run_console() 29 | youtube_client = googleapiclient.discovery.build( 30 | api_service_name, api_version, credentials=credentials) 31 | 32 | return youtube_client 33 | 34 | def get_liked_videos(self): 35 | request = self.youtube_client.videos().list( 36 | part="snippet,contentDetails,statistics", 37 | myRating="like" 38 | ) 39 | response = request.execute() 40 | for item in response["items"]: 41 | video_title = item["snippet"]["title"] 42 | youtube_url = f'https://www.youtube.com/watch?v={item["id"]}' 43 | video = youtube_dl.YoutubeDL({}).extract_info( 44 | youtube_url, download=False) 45 | song_name = video["track"] 46 | artist = video["artist"] 47 | 48 | if song_name is not None and artist is not None: 49 | self.all_song_info[video_title] = { 50 | "youtube_url": youtube_url, 51 | "song_name": song_name, 52 | "artist": artist, 53 | "spotify_uri": self.get_spotify_uri(song_name, artist) 54 | 55 | } 56 | 57 | def get_playlist(self, playlist_name): 58 | query = f'https://api.spotify.com/v1/users/{spotify_user_id}/playlists' 59 | response = requests.get( 60 | query, 61 | headers={ 62 | "Content-Type": "application/json", 63 | "Authorization": f'Bearer {spotify_token}' 64 | } 65 | ) 66 | response_json = response.json() 67 | for playlist in response_json["items"]: 68 | if playlist["name"] == playlist_name: 69 | result = playlist["id"] 70 | 71 | return result 72 | 73 | def get_all_playlist(self): 74 | query = f'https://api.spotify.com/v1/users/{spotify_user_id}/playlists' 75 | response = requests.get( 76 | query, 77 | headers={ 78 | "Content-Type": "application/json", 79 | "Authorization": f'Bearer {spotify_token}' 80 | } 81 | ) 82 | all_playlists = [] 83 | response_json = response.json() 84 | for playlist in response_json["items"]: 85 | all_playlists.append(playlist["name"]) 86 | 87 | return all_playlists 88 | 89 | def create_playlist(self, playlist_name, description): 90 | request_body = json.dumps({ 91 | "name": playlist_name, 92 | "description": description, 93 | "public": True 94 | }) 95 | 96 | query = f'https://api.spotify.com/v1/users/{spotify_user_id}/playlists' 97 | response = requests.post( 98 | query, 99 | data=request_body, 100 | headers={ 101 | "Content-Type": "application/json", 102 | "Authorization": f'Bearer {spotify_token}' 103 | } 104 | ) 105 | response_json = response.json() 106 | return response_json["id"] 107 | 108 | def get_spotify_uri(self, song_name, artist): 109 | query = f'https://api.spotify.com/v1/search?query=track%3A{song_name}+artist%3A{artist}&type=track&offset=0&limit=20' 110 | response = requests.get( 111 | query, 112 | headers={ 113 | "Content-Type": "application/json", 114 | "Authorization": f'Bearer {spotify_token}' 115 | } 116 | ) 117 | response_json = response.json() 118 | songs = response_json["tracks"]["items"] 119 | uri = songs[0]["uri"] 120 | 121 | return uri 122 | 123 | def add_song_to_playlist(self, playlist_name): 124 | self.get_liked_videos() 125 | p_name = playlist_name 126 | uris = [info["spotify_uri"] 127 | for song, info in self.all_song_info.items()] 128 | playlist_id = self.get_playlist(p_name) 129 | q = f'https://api.spotify.com/v1/users/{spotify_user_id}/playlists/{playlist_id}' 130 | res = requests.get( 131 | q, 132 | headers={ 133 | "Content-Type": "application/json", 134 | "Authorization": f'Bearer {spotify_token}' 135 | }) 136 | res_json = res.json() 137 | tracks = res_json["tracks"] 138 | for track in tracks: 139 | t = track["track"] 140 | urri = t["uri"] 141 | if urri in uris: 142 | uris.remove(urri) 143 | 144 | request_data = json.dumps(uris) 145 | 146 | query = f'https://api.spotify.com/v1/playlists/{playlist_id}/tracks' 147 | 148 | response = requests.post( 149 | query, 150 | data=request_data, 151 | headers={ 152 | "Content-Type": "application/json", 153 | "Authorization": f'Bearer {spotify_token}' 154 | } 155 | ) 156 | 157 | if response.status_code != 200: 158 | raise ResponseException(response.status_code) 159 | 160 | 161 | response_json = response.json() 162 | return response_json 163 | 164 | -------------------------------------------------------------------------------- /songs/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /songs/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from .views import HomePageView, Spotify, Apple_Music, New_Spotify, New_Apple_Music, search_spotify_view, search_apple_view, All_Spotify_Playlist, All_Apple_Music_Playlist 4 | 5 | 6 | urlpatterns = [ 7 | path('', HomePageView.as_view(), name='home'), 8 | path('spotify/', Spotify.as_view(), name='spotify'), 9 | path('apple_music/', Apple_Music.as_view(), name='apple_music'), 10 | path('new_spotify/', New_Spotify.as_view(), name='new_spotify'), 11 | path('new_apple_music/', New_Apple_Music.as_view(), name='new_apple_music'), 12 | path('spotify_playlist/', All_Spotify_Playlist.as_view(), name='spotify_playlist'), 13 | path('apple_music_playlist/', All_Apple_Music_Playlist.as_view(), name='apple_music_playlist'), 14 | path('search_spotify/', search_spotify_view, name='search_spotify'), 15 | path('search_apple_music/', search_apple_view, name='search_apple_music'), 16 | ] -------------------------------------------------------------------------------- /songs/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin 2 | from django.views.generic import ListView, TemplateView, DetailView 3 | from .models import Playlist_Spotify, Playlist_Apple_Music, New_Playlist_Spotify, New_Playlist_Apple_Music, All_Playlist_Spotify, All_Playlist_Apple_Music 4 | from django.views.generic.edit import CreateView, UpdateView, DeleteView 5 | from django.urls import reverse_lazy 6 | from django.template.loader import render_to_string 7 | from django.http import JsonResponse 8 | from django.shortcuts import render 9 | from .spotify import ToPlaylist 10 | from .apple_music import A_ToPlaylist 11 | 12 | 13 | 14 | 15 | 16 | 17 | class HomePageView(TemplateView): 18 | template_name = 'index.html' 19 | 20 | class Spotify(LoginRequiredMixin, CreateView): 21 | model = Playlist_Spotify 22 | template_name = 'spotify.html' 23 | login_url = 'login' 24 | fields = {'playlist_name',} 25 | def submit(request): 26 | if request.method == 'POST': 27 | pl_name = request.POST['playlist_name'] 28 | cp = ToPlaylist() 29 | cp.add_song_to_playlist(pl_name) 30 | 31 | class Apple_Music(LoginRequiredMixin, CreateView): 32 | model = Playlist_Apple_Music 33 | template_name = 'apple_music.html' 34 | login_url = 'login' 35 | fields = {'playlist_name',} 36 | def submit(request): 37 | if request.method == 'POST': 38 | pl_name = request.POST['playlist_name'] 39 | cp = A_ToPlaylist() 40 | cp.add_song_to_playlist(pl_name) 41 | 42 | class All_Spotify_Playlist(LoginRequiredMixin, ListView): 43 | template_name = 'all_spotify.html' 44 | model = All_Playlist_Spotify 45 | paginate_by = 5 46 | login_url = 'login' 47 | 48 | class All_Apple_Music_Playlist(LoginRequiredMixin, ListView): 49 | template_name = 'all_apple_music.html' 50 | model = All_Playlist_Apple_Music 51 | paginate_by = 5 52 | login_url = 'login' 53 | 54 | class New_Spotify(LoginRequiredMixin, CreateView): 55 | model = New_Playlist_Spotify 56 | template_name = 'new_spotify.html' 57 | login_url = 'login' 58 | fields = {'description', 'playlist_name',} 59 | def submit(request): 60 | if request.method == 'POST': 61 | pl_name = request.POST['playlist_name'] 62 | des = request.POST['description'] 63 | cp = ToPlaylist() 64 | cp.create_playlist(pl_name, des) 65 | 66 | class New_Apple_Music(LoginRequiredMixin, CreateView): 67 | model = New_Playlist_Apple_Music 68 | template_name = 'new_apple_music.html' 69 | login_url = 'login' 70 | fields = {'description', 'playlist_name',} 71 | def submit(request): 72 | if request.method == 'POST': 73 | pl_name=request.POST['playlist_name'] 74 | des = request.POST['description'] 75 | cp = A_ToPlaylist() 76 | cp.create_playlist(pl_name, des) 77 | 78 | 79 | def search_apple_view(request): 80 | ctx = {} 81 | url_parameter = request.GET.get("q") 82 | 83 | if url_parameter: 84 | all_apple_music_playlist = All_Playlist_Apple_Music.objects.filter(pla_name__icontains=url_parameter) 85 | else: 86 | all_apple_music_playlist = All_Playlist_Apple_Music.objects.all() 87 | 88 | ctx["all_apple_music_playlist"] = all_apple_music_playlist 89 | 90 | if request.is_ajax(): 91 | html = render_to_string( 92 | template_name="all_apple_music_playlist-results-partial.html", 93 | context={"all_apple_music_playlist": all_apple_music_playlist} 94 | ) 95 | 96 | data_dict = {"html_from_view": html} 97 | 98 | return JsonResponse(data=data_dict, safe=False) 99 | 100 | return render(request, "all_apple_music_playlist_search.html", context=ctx) 101 | 102 | 103 | def search_spotify_view(request): 104 | ctx = {} 105 | url_parameter = request.GET.get("q") 106 | 107 | if url_parameter: 108 | all_spotify_playlist = All_Playlist_Spotify.objects.filter(pla_name__icontains=url_parameter) 109 | else: 110 | all_spotify_playlist = All_Playlist_Spotify.objects.all() 111 | 112 | ctx["all_spotify_playlist"] = all_spotify_playlist 113 | 114 | if request.is_ajax(): 115 | html = render_to_string( 116 | template_name="all_spotify_playlist-results-partial.html", 117 | context={"all_spotify_playlist": all_spotify_playlist} 118 | ) 119 | 120 | data_dict = {"html_from_view": html} 121 | 122 | return JsonResponse(data=data_dict, safe=False) 123 | 124 | return render(request, "all_spotify_playlist_search.html", context=ctx) 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /templates/all_apple_music.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}All Playlists Apple Music{% endblock title %} 3 | 4 | {% block content %} 5 |

Music Playlist App

6 |
7 |
8 |

All Playlists  | 

9 | 10 | {% for playlist in object_list %} 11 |
12 |
13 | {{ playlist.pla_name }} 14 |
15 |
16 |
17 | 18 | 19 | {% endfor %} 20 | 21 | {% if is_paginated %} 22 | 23 | 32 | 33 | {% endif %} 34 | 35 | {% endblock content %} -------------------------------------------------------------------------------- /templates/all_apple_music_playlist-results-partial.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% if all_apple_music_playlist %} 7 | {% for playlist in object_list %} 8 |
9 |
10 | {{ playlist.pla_name }} 11 |
12 |
13 |
14 | 15 | 16 | {% endfor %} 17 | 18 | {% else %} 19 |
20 |
21 | No playlist found· 22 |
23 |
24 | {% endif %} 25 | 26 | 27 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /templates/all_apple_music_playlist_search.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Playlists

5 | 6 |
7 | 8 | {# icon and search-box #} 9 |
10 | 11 | 12 |
13 |
14 | {# news-list section #} 15 |
16 | {% include 'all_apple_music_playlist-results-partial.html' %} 17 |
18 | 19 |
20 | 21 | 36 | {% endblock %} 37 | 38 | 40 | 41 | 42 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /templates/all_spotify.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block title %}All Playlists Spotify{% endblock title %} 3 | 4 | {% block content %} 5 |

Music Playlist App

6 |
7 |
8 |

All Playlists  | 

9 | 10 | {% for playlist in object_list %} 11 |
12 |
13 | {{ playlist.pla_name }} 14 |
15 |
16 |
17 | 18 | 19 | {% endfor %} 20 | 21 | {% if is_paginated %} 22 | 23 | 32 | 33 | {% endif %} 34 | 35 | {% endblock content %} -------------------------------------------------------------------------------- /templates/all_spotify_playlist-results-partial.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% if all_spotify_playlist %} 7 | {% for playlist in object_list %} 8 |
9 |
10 | {{ playlist.pla_name }} 11 |
12 |
13 |
14 | 15 | 16 | {% endfor %} 17 | 18 | {% else %} 19 |
20 |
21 | No playlist found· 22 |
23 |
24 | {% endif %} 25 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /templates/all_spotify_playlist_search.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Playlists

5 | 6 |
7 | 8 | {# icon and search-box #} 9 |
10 | 11 | 12 |
13 |
14 | {# news-list section #} 15 |
16 | {% include 'all_spotify_playlist-results-partial.html' %} 17 |
18 | 19 |
20 | 21 | 36 | {% endblock %} 37 | 38 | 40 | 41 | 42 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /templates/apple_music.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

Add Songs to

5 |
6 | {% csrf_token %} 7 | {{ form.as_p }} 8 | 9 | 10 |
11 |
12 |

+ Add Playlist  |  All Playlists

15 | {% endblock content %} -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 46 | 47 | 48 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {% block title %}Music Playlist App{% endblock title %} 61 | 62 | 63 | 96 | 97 |
98 | 99 | {% block content %} 100 | 101 | {% endblock content %} 102 | 103 |
104 | 105 | 116 | 117 | 119 | 120 | 121 | 123 | 124 | 125 | 126 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}Home{% endblock title %} 4 | 5 | {% block content %} 6 |
7 |

Music Playlist App

8 |

A Django app to convert YouTube liked videos to spotify | apple music playlist.

9 |

10 |  |  13 |

14 |
15 | {% endblock content %} -------------------------------------------------------------------------------- /templates/new_apple_music.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

New Playlist for

5 |
6 | {% csrf_token %} 7 | {{ form.as_p }} 8 | 9 | 10 |
11 | {% endblock content %} -------------------------------------------------------------------------------- /templates/new_spotify.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

New Playlist for

5 |
6 | {% csrf_token %} 7 | {{ form.as_p }} 8 | 9 | 10 |
11 | {% endblock content %} -------------------------------------------------------------------------------- /templates/registration/change-password.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}Password Change{% endblock title %} 4 | 5 | {% block content %} 6 |

Password change

7 |

Please enter your old password, for security's sake, and then enter your 8 | new password twice so we can verify you typed it in correctly.

9 | 10 |
11 | {% csrf_token %} 12 | {{ form.as_p }} 13 | 14 |
15 | {% endblock content %} -------------------------------------------------------------------------------- /templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}Log In{% endblock title %} 4 | 5 | {% block content %} 6 | 7 |

Log In

8 | 9 |
10 | {% csrf_token %} 11 | {{ form.as_p }} 12 | 13 |
14 | 15 | {% endblock content %} -------------------------------------------------------------------------------- /templates/spotify.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |

Add Songs to

5 |
6 | {% csrf_token %} 7 | {{ form.as_p }} 8 | 9 |
10 |
11 |

+ Add Playlist  |  All Playlists

14 | {% endblock content %} --------------------------------------------------------------------------------