diff --git a/objects/Encoder.php b/objects/Encoder.php index 789f6c05..78d443aa 100644 --- a/objects/Encoder.php +++ b/objects/Encoder.php @@ -518,6 +518,55 @@ public static function getNext() return false; } + static function isPythonAndPytubeInstalled() + { + $pythonCommand = 'python3 --version'; + $pytubeCommand = 'python3 -m pip show pytube'; + + // Check if Python is installed + exec($pythonCommand, $pythonOutput, $pythonReturnCode); + if ($pythonReturnCode !== 0) { + error_log("Python is not installed. Please install Python on Ubuntu using the following commands:"); + error_log("sudo apt update && sudo apt install -y python3 python3-pip"); + return false; + } + + // Check if pytube is installed + exec($pytubeCommand, $pytubeOutput, $pytubeReturnCode); + if ($pytubeReturnCode !== 0) { + error_log("Pytube is not installed. Install it using pip with the following command:"); + error_log("python3 -m pip install pytube"); + return false; + } + + // Both Python and pytube are installed + return true; + } + + public static function downloadWithPytube($video_url, $filename) + { + global $global; + + $pythonScript = $global['systemRootPath'] . "objects/youtube.py"; + $command = escapeshellcmd("python3 $pythonScript " . escapeshellarg($video_url) . " " . escapeshellarg($filename)); + _error_log("downloadWithPytube($video_url, $filename) " . $command); + exec($command, $output, $return_var); + + $response = new stdClass(); + $response->command = $command; + $response->output = $output; + $response->error = $return_var !== 0; + + if ($response->error) { + $response->msg = "Error downloading video. Check progress.json for details."; + } else { + $response->msg = "Video downloaded successfully."; + } + + return $response; + } + + public static function downloadFile($queue_id) { @@ -578,15 +627,25 @@ public static function downloadFile($queue_id) } if (!empty($q->getVideoDownloadedLink())) { - //begin youtube-dl downloading and symlink it to the video temp file - $response = static::getYoutubeDl($q->getVideoDownloadedLink(), $queue_id, $obj->pathFileName); - if (!empty($response)) { - _error_log("downloadFile:getYoutubeDl SUCCESS queue_id = {$queue_id}"); - $obj->pathFileName = $response; - $obj->error = false; - } else { - _error_log("downloadFile:getYoutubeDl ERROR queue_id = {$queue_id}"); - $obj->error = false; + $videoURL = $q->getVideoDownloadedLink(); + $downloadWithPytubeFilename = ''; + if (self::isPythonAndPytubeInstalled() && isYouTubeUrl($videoURL)) { + $downloadWithPytubeFilename = 'video_download_' . $queue_id; + $response = self::downloadWithPytube($videoURL, $downloadWithPytubeFilename); + } + if(empty($downloadWithPytubeFilename) || $response->error){ + //begin youtube-dl downloading and symlink it to the video temp file + $response = static::getYoutubeDl($videoURL, $queue_id, $obj->pathFileName); + if (!empty($response)) { + _error_log("downloadFile:getYoutubeDl SUCCESS queue_id = {$queue_id}"); + $obj->pathFileName = $response; + $obj->error = false; + } else { + _error_log("downloadFile:getYoutubeDl ERROR queue_id = {$queue_id}"); + $obj->error = false; + } + }else{ + $obj->pathFileName = "{$global['systemRootPath']}videos/pytube/{$downloadWithPytubeFilename}/video.mp4"; } } else { _error_log("downloadFile: not using getYoutubeDl"); @@ -631,7 +690,7 @@ public static function downloadFile($queue_id) _error_log("downloadFile: " . json_encode($obj)); if (empty($obj->error)) { self::setDownloaded($queue_id, $obj->pathFileName); - }else{ + } else { self::setStatusError($queue_id, $obj->msg, 1); } return $obj; @@ -701,14 +760,14 @@ public static function getYoutubeDl($videoURL, $queue_id, $destinationFile, $add if (empty($addOauthFromProvider) && isYouTubeUrl($videoURL) && Encoder::streamerHasOauth('youtube', $streamers_id)) { _error_log("getYoutubeDl: ERROR try oauth "); return self::getYoutubeDl($videoURL, $queue_id, $destinationFile, 'youtube'); - }else{ - if(!empty($addOauthFromProvider)){ + } else { + if (!empty($addOauthFromProvider)) { _error_log("getYoutubeDl: ERROR addOauthFromProvider not empty"); } - if(!isYouTubeUrl($videoURL)){ + if (!isYouTubeUrl($videoURL)) { _error_log("getYoutubeDl: ERROR not a youtube URL $videoURL"); } - if(!Encoder::streamerHasOauth('youtube', $streamers_id)){ + if (!Encoder::streamerHasOauth('youtube', $streamers_id)) { _error_log("getYoutubeDl: ERROR streamers_id does not have oauth streamers_id=$streamers_id"); } } @@ -2122,7 +2181,7 @@ public static function sendToStreamer($target, $postFields, $return_vars = false $time_end = microtime(true); $execution_time = number_format($time_end - $time_start, 3); $error = "sendToStreamer {$url} in {$execution_time} seconds "; - _error_log($error . json_encode($obj).' debug_backtrace='.json_encode(debug_backtrace())); + _error_log($error . json_encode($obj) . ' debug_backtrace=' . json_encode(debug_backtrace())); $newObj = $obj; unset($newObj->postFields); diff --git a/objects/youtube.py b/objects/youtube.py new file mode 100644 index 00000000..0d9d2c94 --- /dev/null +++ b/objects/youtube.py @@ -0,0 +1,119 @@ +import re +import ssl +import os +import json +import sys +import subprocess +import urllib.request + +# Function to ensure pytube is installed +def ensure_pytube_installed(): + try: + import pytube + except ImportError: + print("pytube is not installed. Installing it now...") + subprocess.check_call([sys.executable, "-m", "pip", "install", "pytube"]) + import pytube + return pytube + +# Ensure pytube is available +pytube = ensure_pytube_installed() +from pytube import YouTube +from pytube.innertube import _default_clients +from pytube.exceptions import RegexMatchError + +# Patching pytube for throttling +_default_clients["ANDROID"]["context"]["client"]["clientVersion"] = "19.08.35" +_default_clients["IOS"]["context"]["client"]["clientVersion"] = "19.08.35" +_default_clients["ANDROID_EMBED"]["context"]["client"]["clientVersion"] = "19.08.35" +_default_clients["IOS_EMBED"]["context"]["client"]["clientVersion"] = "19.08.35" +_default_clients["IOS_MUSIC"]["context"]["client"]["clientVersion"] = "6.41" +_default_clients["ANDROID_MUSIC"] = _default_clients["ANDROID"] + +def patched_get_throttling_function_name(js: str) -> str: + function_patterns = [ + r'a\.[a-zA-Z]\s*&&\s*\([a-z]\s*=\s*a\.get\("n"\)\)\s*&&.*?\|\|\s*([a-z]+)', + r'\([a-z]\s*=\s*([a-zA-Z0-9$]+)(\[\d+\])?\([a-z]\)', + r'\([a-z]\s*=\s*([a-zA-Z0-9$]+)(\[\d+\])\([a-z]\)', + ] + for pattern in function_patterns: + regex = re.compile(pattern) + function_match = regex.search(js) + if function_match: + if len(function_match.groups()) == 1: + return function_match.group(1) + idx = function_match.group(2) + if idx: + idx = idx.strip("[]") + array = re.search( + r'var {nfunc}\s*=\s*(\[.+?\]);'.format( + nfunc=re.escape(function_match.group(1))), + js + ) + if array: + array = array.group(1).strip("[]").split(",") + array = [x.strip() for x in array] + return array[int(idx)] + + raise RegexMatchError( + caller="get_throttling_function_name", pattern="multiple" + ) + +ssl._create_default_https_context = ssl._create_unverified_context +pytube.cipher.get_throttling_function_name = patched_get_throttling_function_name + +def save_metadata(yt, folder): + metadata = { + "title": yt.title, + "description": yt.description, + "url": yt.watch_url + } + with open(os.path.join(folder, "metadata.json"), "w") as meta_file: + json.dump(metadata, meta_file, indent=4) + +def save_thumbnail(yt, folder): + thumbnail_url = yt.thumbnail_url + thumbnail_path = os.path.join(folder, "thumbs.jpg") + urllib.request.urlretrieve(thumbnail_url, thumbnail_path) + +def download_video(yt, folder): + video_stream = yt.streams.get_highest_resolution() + video_path = os.path.join(folder, "video.mp4") + yt.register_on_progress_callback(lambda stream, chunk, bytes_remaining: save_progress(stream, bytes_remaining, folder)) + video_stream.download(output_path=folder, filename="video.mp4") + +def save_progress(stream, bytes_remaining, folder): + total_size = stream.filesize + downloaded = total_size - bytes_remaining + progress = { + "total_size": total_size, + "downloaded": downloaded, + "progress": round((downloaded / total_size) * 100, 2) + } + with open(os.path.join(folder, "progress.json"), "w") as progress_file: + json.dump(progress, progress_file, indent=4) + +def main(): + if len(sys.argv) != 3: + print("Usage: python yt_downloader.py ") + sys.exit(1) + + url = sys.argv[1] + folder_name = '../videos/pytube/'+sys.argv[2] + + # Create the folder + os.makedirs(folder_name, exist_ok=True) + + try: + # Download YouTube Video + yt = YouTube(url) + save_metadata(yt, folder_name) + save_thumbnail(yt, folder_name) + download_video(yt, folder_name) + + print(f"Download completed. Files saved in '{folder_name}'.") + except Exception as e: + print(f"Error: {e}") + +if __name__ == "__main__": + main()