├── .gitignore ├── bitchute_dl ├── __init__.py └── __main__.py ├── CONTACT.md ├── LICENSE ├── README.md └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | TODO.txt 2 | vscode.* 3 | dist/ 4 | build/ 5 | bitchute_dl.egg-info/ 6 | -------------------------------------------------------------------------------- /bitchute_dl/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # See __main__.py, this file must exist according to 3 | # https://chriswarrick.com/blog/2014/09/15/python-apps-the-right-way-entry_points-and-scripts/ 4 | # 5 | -------------------------------------------------------------------------------- /CONTACT.md: -------------------------------------------------------------------------------- 1 | # Contact Info 2 | 3 | - Author: Matteljay 4 | - E-mail: Matteljay-at-pm-dot-me 5 | 6 | Donate if you find this app useful, fun, educational or you like to motivate more projects like this. 7 | 8 | ## Cryptocurrency 9 | 10 | BTC: 14VZcizduTvUTesw4T9yAHZ7GjDDmXZmVs 11 | XMR: 4B6YQvbL9jqY3r1cD3ZvrDgGrRpKvifuLVb5cQnYZtapWUNovde7K5rc1LVGw3HhmTiijX21zHKSqjQtwxesBEe6FhufRGS 12 | ETH: 0x7C64707BD877f9cFBf0B304baf200cB1BB197354 13 | DASH: XnMLmmisNAyDMT3Sr1rhpfAPfkMjDyUiwJ 14 | NANO: nano_3yztgrd4exg16r6dwxwc64fasdipi81aoe8yindsin7o31trqsgqanfi9fym 15 | 16 | ## Other methods 17 | 18 | Please contact me for other payment channels. 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2020 Matteljay 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation files 5 | (the "Software"), to deal in the Software without restriction, 6 | including without limitation the rights to use, copy, modify, merge, 7 | publish, distribute, sublicense, and/or sell copies of the Software, 8 | and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 18 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 19 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BitChute-DL 2 | 3 | ## About 4 | 5 | This Python script will download any video from the BitChute video platform to your PC for archival or offline use. The target video will be placed in a clearly named directory based on the publisher's name, a file name will be created based on the video title & publishing date and a check will be done for duplicate files. The project is inspired by [YouTube-DL](https://youtube-dl.org/). 6 | 7 | ## Installation 8 | 9 | You'll need Python >= 3.6 and you can use **pip** to automatically install this command line tool and the required `lxml` dependency: 10 | 11 | sudo pip install bitchute-dl 12 | 13 | If you prefer to download [this repository](https://github.com/Matteljay/bitchute-dl/releases) you could install it after unpacking like so: 14 | 15 | sudo pip install . 16 | 17 | ## Launching 18 | 19 | Here is an example of how to use `bitchute-dl`: 20 | 21 | bitchute-dl https://www.bitchute.com/video/oX3AgHj0jlUX/ 22 | 23 | To keep videos organized, a new folder will be created in the current directory and the target video saved inside: 24 | 25 | ./Matteljay/20200529 Tightcms Set Up Your Own Website Cms Built In Vuejs With Mongodb Docker Container.mp4 26 | 27 | ## Coding details 28 | 29 | Feel free to modify the script's behavior, it is a nice example of a small Python app that will perform web scraping actions based on XPath XML selectors. 30 | 31 | ## Contact info & donations 32 | 33 | More info here: [Contact](CONTACT.md) 34 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | from distutils.core import setup 4 | import setuptools 5 | # Build and push this package with: ./setup.py sdist bdist_wheel && twine upload dist/* 6 | 7 | currentDir = os.path.dirname(os.path.abspath(__file__)) 8 | try: 9 | with open(os.path.join(currentDir, 'README.md'), encoding='utf-8') as fd: 10 | long_description = fd.read() 11 | except Exception: 12 | long_description = '' 13 | 14 | setup( 15 | name='bitchute_dl', 16 | version='1.0.6', 17 | license='MIT', 18 | author='Matteljay', 19 | author_email='matteljay@pm.me', 20 | description='Download any BitChute video to your PC', 21 | long_description = long_description, 22 | long_description_context_type = 'text/markdown', 23 | # scripts = [ 'bitchute-dl' ], 24 | entry_points = {'console_scripts': ['bitchute-dl = bitchute_dl.__main__:main']}, 25 | # install_requires=['lxml'], 26 | install_requires=[ 27 | 'lxml==4.5.0; python_version>="2" and python_version<"3"', 28 | 'lxml; python_version>="3"', 29 | ], 30 | keywords=['bitchute', 'downloader', 'youtube-dl', 'video', 'archive'], 31 | url='https://libersystems.com', 32 | download_url='https://github.com/Matteljay/bitchute-dl/releases', 33 | packages=setuptools.find_packages(), 34 | classifiers=[ 35 | 'Programming Language :: Python :: 3', 36 | 'License :: OSI Approved :: MIT License', 37 | 'Operating System :: OS Independent', 38 | ], 39 | python_requires='>=3.6', 40 | ) 41 | 42 | # EOF 43 | -------------------------------------------------------------------------------- /bitchute_dl/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys, re 3 | from datetime import datetime 4 | import pathlib 5 | import urllib.request 6 | from lxml import html 7 | 8 | def fixEmbedLink(url): 9 | return url.replace('/embed/', '/video/') 10 | 11 | def getTree(url): 12 | if len(url) == 0: 13 | sys.exit('Parameter required, need BitChute URL') 14 | print('Fetching page ' + url) 15 | headers = {} 16 | headers['User-Agent'] = 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:48.0) Gecko/20100101 Firefox/48.0' 17 | req = urllib.request.Request(url, headers = headers) 18 | htmlPage = urllib.request.urlopen(req).read() 19 | print('Parsing HTML...') 20 | return html.document_fromstring(htmlPage) 21 | 22 | def getTarget(tree): 23 | match = tree.xpath('//*[@id="player"]/source') 24 | if len(match) == 0: 25 | sys.exit('Could not find player-source XPath to download') 26 | return match[0].get('src') 27 | 28 | def getTitle(tree): 29 | match = tree.xpath('//*[@id="video-title"]') 30 | if len(match) == 0: 31 | sys.exit('Could not find page title XPath') 32 | title = match[0].text_content() 33 | charsRemovedTitle = re.sub(r'[^\w ]', '', title) 34 | words = charsRemovedTitle.split() 35 | newWords = [] 36 | for word in words: 37 | newWords.append(word.capitalize()) 38 | return ' '.join(newWords) 39 | 40 | def getDate(tree): 41 | match = tree.xpath('//*[@id="video-watch"]/div/div[1]/div[3]/div/div[1]') 42 | if len(match) == 0: 43 | sys.exit('Could not find pubish date XPath') 44 | datePlatform = match[0].text_content() 45 | datePlatformStripped = datePlatform[20:-10] + datePlatform[-8:-2] 46 | dateObject = datetime.strptime(datePlatformStripped, '%H:%M %Z on %B %d, %Y') 47 | return dateObject.strftime('%Y%m%d') 48 | 49 | def getPublisher(tree): 50 | match = tree.xpath('//*[@id="video-watch"]/div/div[1]/div[3]/div/div[2]/div[3]/p[1]/a') 51 | if len(match) == 0: 52 | sys.exit('Could not find publisher name XPath') 53 | publisherSpaced = match[0].text_content() 54 | return re.sub(r'[^\w]', '_', publisherSpaced) 55 | 56 | def getExtension(target): 57 | return pathlib.Path(target).suffix 58 | 59 | def downloadVideo(target, publisher, date, title, extension): 60 | outputFile = f'{date} {title}{extension}' 61 | pathlib.Path(publisher).mkdir(parents=True, exist_ok=True) 62 | outputFull = publisher + '/' + outputFile 63 | if pathlib.Path(outputFull).is_file(): 64 | sys.exit('File already exists: ' + outputFull) 65 | print(f'Downloading video as: ' + outputFull) 66 | urllib.request.urlretrieve(target, outputFull) 67 | print('Done.') 68 | 69 | def main(args=None): 70 | if args is None: 71 | args = sys.argv[1:] 72 | url = fixEmbedLink(' '.join(args)) 73 | tree = getTree(url) 74 | target = getTarget(tree) 75 | publisher = getPublisher(tree) 76 | date = getDate(tree) 77 | title = getTitle(tree) 78 | extension = getExtension(target) 79 | downloadVideo(target, publisher, date, title, extension) 80 | 81 | if __name__ == '__main__': 82 | sys.exit(main()) 83 | 84 | # EOF 85 | --------------------------------------------------------------------------------