diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b6e4761..0000000 --- a/.gitignore +++ /dev/null @@ -1,129 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ diff --git a/README.md b/README.md index 4782620..6222796 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,70 @@ # Facebook-Downloader -A program for downloading Facebook videos +A program for downloading Facebook videos, given a video url # Installation -**1. Clone the project** +**Install from PyPI** ``` -git clone https://github.com/rly0nheart/Facebook-Downloader.git +pip install facebook-downloader ``` -**2. Move to Facebook-Downloader directory** +### Note +> In order to run the program, You will need to have the FireFox browser installed on your pc +> The program is dependent on selenium, so in order to run it, you will have to download and properly setup geckodriver (setup instructions available below) +>> + +# Geckodriver setup +## Linux +**1. Go to the geckodriver [releases page](https://github.com/mozilla/geckodriver/releases/). Find the latest version of the driver for your platform and download it** + +**2. Extract the downloaded file** ``` -cd Facebook-Downloader +tar -xvzf geckodriver* ``` -**3. Install dependencies** -## Note -> *This will install tqdm, selenium, and requests* -> > *You will need to have Firefox installed to run the program* -> > > *For user convenience, the program will come with a geckodriver.exe binary* +**3. Make it executable** ``` -pip install -r requirements.txt +chmod +x geckodriver ``` +**4. Add geckodriver to your system path** +``` +export PATH=$PATH:/path/to/downloaded/geckodriver +``` + +### Note +> If you encounter issues with the above commands, then you should run them as root (with sudo) + + +## Windows +**1. Go to the geckodriver [releases page](https://github.com/mozilla/geckodriver/releases/). Find the geckodriver.exe binary for your platform and download it** + +**2. Move the downloaded executable to** *C:\Users\yourusername\AppData\Local\Programs\Python\Python310* + +### Note +> The numbers on the directory 'Python310' will depend on the version of Python you have + +## Mac OS +* [Set up Selenium & GeckoDriver (Mac)](https://medium.com/dropout-analytics/selenium-and-geckodriver-on-mac-b411dbfe61bc) + + # Usage ``` -python downloader.py +facebook_downloader ``` -> *Alternatively, you could grant execution permission to the downloader and run it as shown below* - -**1. Grant execution permission** -``` -chmod +x downloader.py -``` - -**2. Run downloader** -``` -./downloader.py -``` - -## Example -``` -python downloader.py https://www.facebook.com/PageName/videos/VideoID -``` - -## Note -> Upon run, the downloader will first check for updates. If found, users will be prompted to download the updates +### Note +> Upon run, the program will first check for updates. If found, users will be notified about the update # Optional Arguments | Flag | Description | |---------|:-----------:| -| *-A/--audio* | download audio only (coming soon) | -| *-o/--output* | output filename | -| *-v/--version* | show program's version number and exit | +| *-a/--audio* | download file as audio | +| *-o/--output* | output file name | # Donations If you would like to donate, you could Buy A Coffee for the developer using the button below Buy Me A Coffee -Your support will be much appreciated! +Your support will be much appreciated! \ No newline at end of file diff --git a/downloader.py b/downloader.py deleted file mode 100644 index da88aa3..0000000 --- a/downloader.py +++ /dev/null @@ -1,100 +0,0 @@ -import time -import logging -import argparse -import requests -from tqdm import tqdm -from selenium import webdriver -from selenium.webdriver.common.by import By -from selenium.webdriver.common.keys import Keys -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support import expected_conditions - - -option = webdriver.FirefoxOptions() -option.add_argument('--headless') -driver = webdriver.Firefox(options=option) -program_version_number = "2022.1.0.0" -downloading_url = "https://getfvid.com" -update_check_endpoint = "https://api.github.com/repos/rly0nheart/facebook-downloader/releases/latest" - - -def notice(): - notice_msg = f""" -facebook-downloader {program_version_number} Copyright (C) 2022 Richard Mwewa - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. -""" - print(notice_msg) - - -def check_and_get_updates(): - notice() - response = requests.get(update_check_endpoint).json() - if response['tag_name'] == program_version_number: - """Ignore if the program is up to date""" - pass - else: - update_prompt = input(f"[?] A new release is available ({response['tag_name']}). Would you like to install it? (y/n) ") - if update_prompt.lower() == "y": - files_to_update = ['downloader.py', 'geckodriver.exe', 'README.md', 'requirements.txt'] - for file in tqdm(files_to_update, desc=f'Updating'): - data = requests.get(f'https://raw.githubusercontent.com/rly0nheart/facebook-downloader/master/{file}') - with open(file, "wb") as f: - f.write(data.content) - f.close() - print("Updated: Re-run program.") - else: - pass - - -def download_video(url, output): - driver.get(downloading_url) # Opening getfvid.com, a website that downloads facebook videos - url_entry_field = driver.find_element(By.NAME, "url") # Find the url entry field - url_entry_field.send_keys(url) # write facebook url in the entry field - url_entry_field.send_keys(Keys.ENTER) # press enter - print('Please standby (20 seconds)...') - time.sleep(20) # Sleep for at least 20 seconds to wait for the next page to load - driver.refresh - - """ - HD: "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[1]/a" - SD: "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[2]/a" - Audio: "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[3]/a" - """ - - download_btn = WebDriverWait(driver, 20).until(expected_conditions.presence_of_element_located((By.XPATH, '/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[1]/a'))) # Find the download button (this clicks the first button which returns a video in hd) - download_url = download_btn.get_attribute('href') - - with requests.get(download_url, stream=True) as response: - response.raise_for_status() - with open(f'downloads/{output}.mp4', 'wb') as file: - for chunk in tqdm(response.iter_content(chunk_size=8192), desc=f'Downloading: {output}.mp4'): - file.write(chunk) - print(f'Downloaded: {file.name}') - driver.close() - - -parser = argparse.ArgumentParser(description='facebook-downloader — by Richard Mwewa') -parser.add_argument('url', help='facebook video url (eg. https://www.facebook.com/PageName/videos/VideoID') -parser.add_argument('-A', '--audio', help=argparse.SUPPRESS, action='store_true') -parser.add_argument('-o', '--output', help='output filename') -parser.add_argument('-v', '--version', version='2022.1.0.0', action='version') -args = parser.parse_args() -url = args.url -output = args.output - - -if __name__ == "__main__": - try: - check_and_get_updates() - download_video(url, output) - - except KeyboardInterrupt: - print('Process interrupted with Ctrl+C.') - - except Exception as e: - print('An error occured:', e) - diff --git a/downloads/.nomedia b/downloads/.nomedia deleted file mode 100644 index 8b13789..0000000 --- a/downloads/.nomedia +++ /dev/null @@ -1 +0,0 @@ - diff --git a/facebook_downloader/__init__.py b/facebook_downloader/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/facebook_downloader/downloader.py b/facebook_downloader/downloader.py new file mode 100644 index 0000000..49ca42c --- /dev/null +++ b/facebook_downloader/downloader.py @@ -0,0 +1,88 @@ +import os +import argparse +import requests +from tqdm import tqdm +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions + + +class FacebookDownloader: + def __init__(self): + parser = argparse.ArgumentParser(description='facebook-downloader — by Richard Mwewa') + parser.add_argument('url', help='facebook video url (eg. https://www.facebook.com/PageName/videos/VideoID') + parser.add_argument('-a', '--audio', help='download file as audio', action='store_true') + parser.add_argument('-o', '--output', help='output filename') + self.args = parser.parse_args() + + option = webdriver.FirefoxOptions() + option.add_argument('--headless') + self.driver = webdriver.Firefox(options=option) + + self.program_version_number = "2022.1.0.0" + self.downloading_url = "https://getfvid.com" + self.update_check_endpoint = "https://api.github.com/repos/rly0nheart/facebook-downloader/releases/latest" + + + def notice(self): + notice_msg = f""" + facebook-downloader {self.program_version_number} Copyright (C) 2022 Richard Mwewa + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + """ + print(notice_msg) + + + def check_updates(self): + self.notice() + response = requests.get(self.update_check_endpoint).json() + if response['tag_name'] == self.program_version_number: + """Ignore if the program is up to date""" + pass + else: + print(f"[!] A new release is available ({response['tag_name']}). Run 'pip install --upgrade facebook-downloader' to get the updates.") + + + def download_type(self): + if self.args.audio: + download_type_element = "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[3]/a" + else: + download_type_element = "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[1]/a" + + return download_type_element + + + def path_finder(self): + directory_list = [os.path.join('downloads', 'videos'), os.path.join('downloads', 'audio')] + for directory in directory_list: + os.makedirs(directory, exist_ok=True) + + + def download_video(self): + self.path_finder() + self.check_updates() + self.driver.get(self.downloading_url) # Opening getfvid.com, a website that downloads facebook videos + url_entry_field = self.driver.find_element(By.NAME, "url") # Find the url entry field + url_entry_field.send_keys(self.args.url) # write facebook url in the entry field + url_entry_field.send_keys(Keys.ENTER) # press enter + print('Please standby (20 seconds)...') + self.driver.refresh + """ + HD: "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[1]/a" + SD: "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[2]/a" + Audio: "/html/body/div[2]/div/div/div[1]/div/div[2]/div/div[3]/p[3]/a" + """ + download_btn = WebDriverWait(self.driver, 20).until(expected_conditions.presence_of_element_located((By.XPATH, self.download_type()))) # Find the download button (this clicks the first button which returns a video in hd) + download_url = download_btn.get_attribute('href') + + with requests.get(download_url, stream=True) as response: + response.raise_for_status() + with open(os.path.join('downloads', 'videos', f'{self.args.output}.mp4'), 'wb') as file: + for chunk in tqdm(response.iter_content(chunk_size=8192), desc=f'Downloading: {self.args.output}.mp4'): + file.write(chunk) + print(f'Downloaded: {file.name}') + self.driver.close() \ No newline at end of file diff --git a/facebook_downloader/main.py b/facebook_downloader/main.py new file mode 100644 index 0000000..0e1a2b5 --- /dev/null +++ b/facebook_downloader/main.py @@ -0,0 +1,12 @@ +from facebook_downloader.downloader import FacebookDownloader + +def main(): + try: + start = FacebookDownloader() + start.download_video() + + except KeyboardInterrupt: + print('Process interrupted with Ctrl+C.') + + except Exception as e: + print('An error occured:', e) \ No newline at end of file diff --git a/geckodriver.exe b/geckodriver.exe deleted file mode 100644 index 87b2b58..0000000 Binary files a/geckodriver.exe and /dev/null differ diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index f721993..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -tqdm -selenium -requests diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..8c4d007 --- /dev/null +++ b/setup.py @@ -0,0 +1,31 @@ +import setuptools + +with open('README.md', 'r', encoding='utf-8') as file: + long_description = file.read() + +setuptools.setup( + name='facebook-downloader', + version='2022.1.0.0', + author='Richard Mwewa', + author_email='rly0nheart@duck.com', + packages=['facebook_downloader'], + description='Facebook video downloader', + long_description=long_description, + long_description_content_type='text/markdown', + url='https://github.com/rly0nheart/facebook-downloader', + license='GNU General Public License v3 (GPLv3)', + install_requires=['requests', 'selenium', 'tqdm'], + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Information Technology', + 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', + 'Operating System :: OS Independent', + 'Natural Language :: English', + 'Programming Language :: Python :: 3' + ], + entry_points={ + 'console_scripts': [ + 'facebook_downloader=facebook_downloader.main:main', + ] + }, +) diff --git a/test_download_video.py b/test_download_video.py deleted file mode 100644 index 0764dc0..0000000 --- a/test_download_video.py +++ /dev/null @@ -1,6 +0,0 @@ -from downloader import download_video - -def test_download_video(): - # I find this video very interesting, enjoy! ;) - url = 'https://www.facebook.com/VICE/videos/663211078474482' - download_video(url, output='test_video_making-a-weed-smoothie')