Skip to content

Commit

Permalink
https://github.com/WWBN/AVideo/issues/9242
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Neto committed Nov 27, 2024
1 parent c236c1d commit 7f1b6ce
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 15 deletions.
89 changes: 74 additions & 15 deletions objects/Encoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");
}
}
Expand Down Expand Up @@ -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);
Expand Down
119 changes: 119 additions & 0 deletions objects/youtube.py
Original file line number Diff line number Diff line change
@@ -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 <YouTube_URL> <Folder_Name>")
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()

0 comments on commit 7f1b6ce

Please sign in to comment.