From b7923212c65645cf5844fd11a792e9fa4fce9ca2 Mon Sep 17 00:00:00 2001 From: Fred Impraim <41412364+feimpraim@users.noreply.github.com> Date: Sat, 2 Sep 2023 23:12:51 -0700 Subject: [PATCH] initial comit --- .DS_Store | Bin 0 -> 6148 bytes .vscode/settings.json | 4 + app.py | 190 ++++++++++++++++++++++++++++++++++++---- requirements.txt | 4 + templates/error.html | 59 +++++++++++++ templates/hello.html | 22 +++-- templates/index.html | 63 ++++++++----- templates/login.html | 27 ++++++ templates/progress.html | 73 +++++++++++++++ 9 files changed, 394 insertions(+), 48 deletions(-) create mode 100644 .DS_Store create mode 100644 .vscode/settings.json create mode 100644 templates/error.html create mode 100644 templates/login.html create mode 100644 templates/progress.html diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..f3c73bdfba537db4b9e696605d5fad5a12bebd43 GIT binary patch literal 6148 zcmeHK$xZ@65Phx0xFvG*VlqD4lp7JA(K#YqAi_0TsE9ZVqed*CgJy>Jk>M?oS0n2LN2s8PQ}!x4%NVv+IGlB!nJS#2OvW+& zIL=!p;}#D2^?G1?MSv;>=;H>rSY_rX_GGMuCff8byxQQbYg8{nm+NUT;!f|MFkh8( z)zzO7pYVuFVxJLBt)+*}^!kldGpz9(*IlM=CC)Ow{?APPd9D4Pb>C>O!)opjGi!nV z8F4z})K!K*!^oIVHYPtx$Ldo}R~K{k4xfKp87=fP40)cz3|@Mh7zK>2WOiXF^r{iJaBQL z!^EPc!??qTahZ)fp%~51`2!7y2`zf>74QnA6_~b{dD;KVzn}lpB!A@<@Cy7Z1x&7V zTq<%&Zf_k~ob0s$+c}$<#3dG$Lg%()?Z{TV%%;YBfpmzW!^9#-X#S6Y%HW+>;7=9! E2D(J1lmGw# literal 0 HcmV?d00001 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..6c0a3892c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "appService.defaultWebAppToDeploy": "/subscriptions/2fa1771c-889e-44af-96c2-48ad38845554/resourceGroups/FredImpYouTubeDownloader/providers/Microsoft.Web/sites/FredImpYouTubeDownloader", + "appService.deploySubpath": "." +} \ No newline at end of file diff --git a/app.py b/app.py index 3d1808cf6..224e30b47 100644 --- a/app.py +++ b/app.py @@ -1,32 +1,188 @@ import os +import certifi +import ssl +import zipfile +from functools import wraps +from pytube import YouTube +from pytube.cli import on_progress +from pytube.exceptions import PytubeError, VideoUnavailable, VideoPrivate, LiveStreamError +from flask_oauthlib.client import OAuth +from io import BytesIO +from flask import Flask, redirect, render_template, request, send_file, url_for, session +from dotenv import load_dotenv -from flask import (Flask, redirect, render_template, request, - send_from_directory, url_for) +ssl._create_default_https_context = ssl._create_unverified_context app = Flask(__name__) +secret_key = os.urandom(24) +app.secret_key = secret_key # Change this to a more secure secret key + +oauth = OAuth(app) + +google = oauth.remote_app( + 'google', + consumer_key='4replace, + consumer_secret='replace', + request_token_params={'scope': 'email'}, + base_url='https://www.googleapis.com/oauth2/v1/', + request_token_url=None, + access_token_method='POST', + access_token_url='https://accounts.google.com/o/oauth2/token', + authorize_url='https://accounts.google.com/o/oauth2/auth', +) + +app.config['OAUTH_CREDENTIALS'] = { + 'google': { + 'id': 'replcae', + 'secret': 'repalce', + 'redirect_uri': 'replace', + }, +} + +# Dictionary to store download progress for each user +download_progress = {} + +def login_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if 'user_email' not in session: + return redirect(url_for('login_page', next=request.url)) + return f(*args, **kwargs) + return decorated_function + +@app.route('/login_page') +def login_page(): + if 'user_email' in session: + return redirect(url_for('index')) + return render_template("login.html") @app.route('/') +@login_required def index(): - print('Request for index page received') - return render_template('index.html') + return render_template("index.html") + +@app.route('/login') +def login(): + return google.authorize(callback=url_for('authorized', _external=True)) + +@app.route('/logout') +def logout(): + session.pop('google_token', None) + session.pop('user_email', None) + return redirect(url_for('login_page')) + +@app.route('/login/authorized') +def authorized(): + response = google.authorized_response() + + if response is None or response.get('access_token') is None: + return 'Access denied: reason={} error={}'.format( + request.args['error_reason'], + request.args['error_description'] + ) + + session['google_token'] = (response['access_token'], '') + user_info = google.get('userinfo') + session['user_email'] = user_info.data['email'] + + return redirect(url_for('index')) + +def download_audio_and_video(yt): + try: + audio = yt.streams.filter(only_audio=True, file_extension='mp4').order_by('abr').desc().first() + video = yt.streams.filter(progressive=True, file_extension='mp4').order_by('resolution').desc().first() + + if not audio: + return None, "Couldn't find a suitable audio stream for the provided URL." + if not video: + return None, "Couldn't find a suitable video stream for the provided URL." + + audio_buffer = BytesIO() + video_buffer = BytesIO() -@app.route('/favicon.ico') -def favicon(): - return send_from_directory(os.path.join(app.root_path, 'static'), - 'favicon.ico', mimetype='image/vnd.microsoft.icon') + audio.stream_to_buffer(audio_buffer) + video.stream_to_buffer(video_buffer) + + audio_buffer.seek(0) + video_buffer.seek(0) + + return audio_buffer, video_buffer, None + except PytubeError as e: + return None, None, f"An error occurred while processing the audio and video: {str(e)}" + + try: + audio = yt.streams.filter(only_audio=True).order_by('abr').desc().first() + video = yt.streams.filter(progressive=True, file_extension='mp4').order_by('resolution').desc().first() + + if not audio: + return None, "Couldn't find a suitable audio stream for the provided URL." + if not video: + return None, "Couldn't find a suitable video stream for the provided URL." + + audio_buffer = BytesIO() + video_buffer = BytesIO() + + audio.stream_to_buffer(audio_buffer) + video.stream_to_buffer(video_buffer) + + audio_buffer.seek(0) + video_buffer.seek(0) + + return audio_buffer, video_buffer, None + except PytubeError as e: + return None, None, f"An error occurred while processing the audio and video: {str(e)}" @app.route('/hello', methods=['POST']) +@login_required def hello(): - name = request.form.get('name') + url = request.form.get('name') + if not url: + return render_template("error.html", message="URL is missing. Please provide a valid YouTube URL.", back_url=url_for('index')) + + try: + yt = YouTube(url, on_progress_callback=on_progress) + + audio_buffer, video_buffer, download_error = download_audio_and_video(yt) + if download_error: + return render_template("error.html", message=download_error, back_url=url_for('index')) + + # Create a ZIP archive containing audio and video + zip_buffer = BytesIO() + with zipfile.ZipFile(zip_buffer, 'w') as zip_file: + zip_file.writestr(f"{yt.title}.mp3", audio_buffer.read()) + zip_file.writestr(f"{yt.title}.mp4", video_buffer.read()) + + zip_buffer.seek(0) + + return send_file( + zip_buffer, + as_attachment=True, + attachment_filename=f"{yt.title}.zip", + mimetype="application/zip", + ) + except VideoUnavailable: + return render_template("error.html", message="The provided YouTube video is unavailable.", back_url=url_for('index')) + except VideoPrivate: + return render_template("error.html", message="The provided YouTube video is private and cannot be accessed.", back_url=url_for('index')) + except LiveStreamError: + return render_template("error.html", message="Live streams cannot be downloaded.", back_url=url_for('index')) + except PytubeError as e: + return render_template("error.html", message=f"An error occurred while processing the video: {str(e)}", back_url=url_for('index')) + +@app.route('/error', methods=['GET']) +@login_required +def error(): + message = request.args.get('message') + back_url = request.args.get('back_url') + return render_template("error.html", message=message, back_url=back_url) - if name: - print('Request for hello page received with name=%s' % name) - return render_template('hello.html', name = name) - else: - print('Request for hello page received with no name or blank name -- redirecting') - return redirect(url_for('index')) +@google.tokengetter +def get_google_oauth_token(): + return session.get('google_token') +if __name__ == "__main__": + # Set the SSL certificate file for urllib + os.environ["SSL_CERT_FILE"] = certifi.where() -if __name__ == '__main__': - app.run() + app.run(debug=True, port=5000) diff --git a/requirements.txt b/requirements.txt index a3c7c6ad6..6bd8d6968 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,6 @@ Flask==2.0.2 gunicorn +pytube +certifi +moviepy +Flask-OAuthlib \ No newline at end of file diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 000000000..58a7e10bf --- /dev/null +++ b/templates/error.html @@ -0,0 +1,59 @@ + + + + + Error + + + + +
+

Error

+

{{ message }}

+ Back to Enter URL +
+ + + \ No newline at end of file diff --git a/templates/hello.html b/templates/hello.html index c277e7740..d5fcb0d07 100644 --- a/templates/hello.html +++ b/templates/hello.html @@ -1,21 +1,25 @@ + Hello Azure - Python Quickstart - -
+ + +
- Azure Logo - -

Hello {{name}}

+ Azure Logo +

The YouTube Video with Title {{name}} Has been downloaded

- It is nice to meet you! + {% if 'user_email' in session %} Logged in as: {{ session['user_email'] }} + Logout from Google {% else %} + Login with Google {% endif %}

Back home -
-
- + +
+ + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 9cdba30cd..e7459a060 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,30 +1,49 @@ + + - Hello Azure - Python Quickstart + + + Fred's YouTube Video Audio Downloader + - - -
-
- Azure Logo - -

Welcome to Azure

-
-
-
- - -
- -
-
- -
+ +
+
+
+ Azure Logo +

Fred's YouTube Video and Audio downloader

+
+ +
+
+
+ + +
+
+ +
+
+
+ +
+

Logged in as: {{ session['user_email'] }}

+ Logout from Google
- -
- +
+
+ + \ No newline at end of file diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 000000000..6a01f0920 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,27 @@ + + + + + + + Fred's YouTube Downloader - Login + + + + + +
+
+
+ Azure Logo +

Welcome to Fred's YouTube Downloader

+

+ Please login to continue. This is just to ensure you are not a bot +

+ Login with Google +
+
+
+ + + \ No newline at end of file diff --git a/templates/progress.html b/templates/progress.html new file mode 100644 index 000000000..e6a8eabd1 --- /dev/null +++ b/templates/progress.html @@ -0,0 +1,73 @@ + + + + + + + Download Progress + + + + + +
+

Download Progress

+
+
+
+
+

Download completed successfully!

+
+
+ + + + + \ No newline at end of file