├── .gitignore ├── .idea └── vcs.xml ├── CONTRIBUTING.md ├── LICENSE ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── downloader.py ├── extractor.py ├── main.py └── storage.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | __pycache__/ 3 | __pycache__/* -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | To contribute please fork the Project and start a Pull request that contains the features added/bugs fixed. 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Julius Noack 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 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Feature_Title 2 | 3 | - added new Feature xy 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://scrutinizer-ci.com/g/Jnoack331/pornhub-pluenderer/badges/build.png?b=master)](https://scrutinizer-ci.com/g/Jnoack331/pornhub-pluenderer/build-status/master) 2 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Jnoack331/pornhub-pluenderer/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Jnoack331/pornhub-pluenderer/?branch=master) 3 | [![Code Coverage](https://scrutinizer-ci.com/g/Jnoack331/pornhub-pluenderer/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/Jnoack331/pornhub-pluenderer/?branch=master) 4 | # pornhub-pluenderer 5 | you know what it does 6 | -------------------------------------------------------------------------------- /downloader.py: -------------------------------------------------------------------------------- 1 | import requests as http 2 | from storage import Storage 3 | 4 | 5 | class Downloader(object): 6 | def __init__(self): 7 | self.storage = Storage() 8 | 9 | @classmethod 10 | def get(cls, url): 11 | rsp = http.get(url) 12 | return rsp.text 13 | 14 | def save_file(self, url, name): 15 | file = self.storage.new_file(name) 16 | if file: 17 | rsp = http.get(url, stream=True) 18 | for chunk in rsp.iter_content(chunk_size=1000 * 1024): 19 | if chunk: # filter out "keep-alive" chunks 20 | file.write(chunk) 21 | -------------------------------------------------------------------------------- /extractor.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | 4 | VIEW_KEY_RE = r"a.+?href=\".+?viewkey=(.+?)\"" 5 | INFO_RE = r"var flashvars_.+? = ({.+})" 6 | 7 | 8 | class Extractor(object): 9 | def __init__(self): 10 | self._viewkey_re = re.compile(VIEW_KEY_RE, re.MULTILINE) 11 | self._videoinfo_re = re.compile(INFO_RE, re.MULTILINE) 12 | 13 | def get_viewkeys(self, data): 14 | matches = re.findall(self._viewkey_re, data) 15 | 16 | # convert to a set to eliminate redundant viewkeys 17 | return set(matches) 18 | 19 | def get_video_info(self, data): 20 | info_json = re.search(self._videoinfo_re, data) 21 | if info_json is None: 22 | return None 23 | return json.loads(info_json.group(1)) 24 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import signal 3 | from clint.textui import colored, puts 4 | from downloader import Downloader 5 | from extractor import Extractor 6 | 7 | signal.signal(signal.SIGINT, lambda x, y: sys.exit(0)) 8 | 9 | 10 | def main(): 11 | downloader = Downloader() 12 | extractor = Extractor() 13 | url = "https://pornhub.com" 14 | 15 | puts(colored.green("getting video keys.")) 16 | main_page = downloader.get(url) 17 | view_keys = extractor.get_viewkeys(main_page) 18 | 19 | puts(colored.green("starting to download videos.")) 20 | for key in view_keys: 21 | puts(colored.green("getting video information.")) 22 | absolute_url = "https://pornhub.com/view_video.php?viewkey=" + key 23 | page = downloader.get(absolute_url) 24 | info = extractor.get_video_info(page) 25 | 26 | if info is None: 27 | continue 28 | 29 | hd_quality = info['mediaDefinitions'][0] 30 | puts(colored.green("downloading video %s." % info['video_title'])) 31 | downloader.save_file(hd_quality["videoUrl"], info['video_title'] + ".mp4") 32 | 33 | 34 | if __name__ == "__main__": 35 | main() 36 | -------------------------------------------------------------------------------- /storage.py: -------------------------------------------------------------------------------- 1 | from os import path, makedirs 2 | 3 | download_folder = "download/" 4 | 5 | 6 | class AlreadyDownloadedError(Exception): 7 | def __init__(self, filename): 8 | message = "The file %s is skipped as it already exists." 9 | Exception.__init__(self, message % filename) 10 | 11 | 12 | class Storage(object): 13 | def __init__(self): 14 | self._skipped_files = [] 15 | 16 | def new_file(self, filename): 17 | if path.exists(filename): 18 | self._skipped_files.append(filename) 19 | raise AlreadyDownloadedError(filename) 20 | 21 | if path.exists(download_folder) is not True: 22 | makedirs(download_folder) 23 | 24 | return open(download_folder + filename, "wb") 25 | 26 | @property 27 | def skipped_files(self): 28 | return self._skipped_files 29 | --------------------------------------------------------------------------------