diff --git a/app/sound.py b/app/sound.py index f39af93..aa7309f 100644 --- a/app/sound.py +++ b/app/sound.py @@ -1,5 +1,5 @@ from app import app, db, limiter -from app.sound_models import YouTubeAudio, SoundcloudAudio +from app.sound_models import YouTubeAudio, SoundcloudAudio, CouldNotDecode, CouldNotDownload, CouldNotProcess from flask import Response, send_file, redirect, url_for, render_template, request, jsonify from multiprocessing import Value from mutagen.mp3 import MP3 @@ -31,6 +31,23 @@ def get_youtube(mediaid): db.session.commit() return audio +basic_responses = { + CouldNotDecode : 'Could not decode process response.', + CouldNotDownload : 'Could not download video.', + CouldNotProcess : 'Could not process.' +} + +# A simple function among the routes to determine what should be returned. +def errorCheck(e, auth=False): + response = str(e) + print(response) + if auth: + if type(e) in basic_responses.keys(): + response += f'\n{basic_responses[type(e)]}' + else: + raise e + return Response(response, status=200, mimetype='text/plain') + # Under the request context, it grabs the same args needed to decide whether the stream has been downloaded previously # It applies rate limiting differently based on service, and whether the stream has been accessed previously def downloadLimiter(): @@ -48,7 +65,10 @@ def downloadLimiter(): def stream(service, mediaid): if service == 'youtube': if YouTubeAudio.isValid(mediaid): - audio = get_youtube(mediaid) + try: + audio = get_youtube(mediaid) + except Exception as e: + return errorCheck(e) return send_file(audio.getPath(alt=True), attachment_filename=audio.filename) else: return getInvalidID() diff --git a/app/sound_models.py b/app/sound_models.py index 5653fa9..a3b5f42 100644 --- a/app/sound_models.py +++ b/app/sound_models.py @@ -6,7 +6,8 @@ import os import re # Returned when a erroring status code is returned. May end up hitting false positives, where the file was still produced properly -# yet a erroring status code was returned. May be a good measure to always disconnect when a error code is found +# yet a erroring status code was returned. May be a good measure to always disconnect when a error code is found. +# Takes precedence over CouldNotDownload and CouldNotDecode exceptions. class CouldNotProcess(Exception): pass @@ -58,16 +59,16 @@ class YouTubeAudio(db.Model): print(f'Filling out metadata for {self.id}') # Use stdout=PIPE, [Python 3.6] production server support instead of 'capture_output=True' => 'process.stdout' self.filename = self.id + '.mp3' - print(f'Filename acquired for {self.id}') - processJSON = subprocess.Popen(f'youtube-dl -4 -x --audio-format mp3 --restrict-filenames --dump-json {self.id}'.split(' '), - encoding='utf-8', stdout=subprocess.PIPE) - data = processJSON.communicate()[0] - - + command = f'youtube-dl -4 -x --audio-format mp3 --restrict-filenames --dump-json {self.id}' + processJSON = subprocess.Popen(command.split(' '), + encoding='utf-8', stdout=subprocess.PIPE, stderr=subprocess.PIPE) + data = processJSON.communicate() + if processJSON.returncode != 0: + raise CouldNotProcess(f'> {command}\n{data[1]}Exit Code: {processJSON.returncode}') try: - data = json.loads(data) + data = json.loads(data[0]) except json.JSONDecodeError: - raise CouldNotDecode(data) + raise CouldNotDecode(data) # We'll return the process data, figure out what to do with it higher up in stack (return/diagnose etc.) print(f'JSON acquired for {self.id}, beginning to fill.') self.duration = data['duration'] self.url = data['webpage_url'] # Could be created, but we'll just infer from JSON response @@ -80,7 +81,11 @@ class YouTubeAudio(db.Model): # Begins the download process for a video def download(self): print(f'Attempting download of {self.id}') - subprocess.run(f'youtube-dl -x -4 --restrict-filenames --audio-quality 64K --audio-format mp3 -o ./app/sounds/youtube/%(id)s.%(ext)s {self.id}'.split(' ')) + process = subprocess.Popen(f'youtube-dl -x -4 --restrict-filenames --audio-quality 64K --audio-format mp3 -o ./app/sounds/youtube/%(id)s.%(ext)s {self.id}'.split(' ')) + if process.returncode != 0: + raise CouldNotProcess(data + f'\Exit Code: {processJSON.returncode}') + if not os.path.exists(self.getPath()): + raise CouldNotDownload(process.communicate()[0]) print(f'Download attempt for {self.id} finished.') # Validates whether the specified ID could be a valid YouTube video ID