|
@@ -14,7 +14,7 @@ from googleapiclient.discovery import build
|
|
|
import subprocess
|
|
import subprocess
|
|
|
from google.auth.transport.requests import Request
|
|
from google.auth.transport.requests import Request
|
|
|
import sys
|
|
import sys
|
|
|
-from auth import get_token, get_token_non_interactive, get_service_account_token
|
|
|
|
|
|
|
+from auth import get_token, get_token_non_interactive, get_service_account_token, setup_youtube_access
|
|
|
|
|
|
|
|
# Home Assistant Integration
|
|
# Home Assistant Integration
|
|
|
try:
|
|
try:
|
|
@@ -235,6 +235,63 @@ def update_yt_dlp():
|
|
|
logger.debug(f"yt-dlp update error: {e.stderr}")
|
|
logger.debug(f"yt-dlp update error: {e.stderr}")
|
|
|
raise
|
|
raise
|
|
|
|
|
|
|
|
|
|
+def get_youtube_cookie_args():
|
|
|
|
|
+ """Get YouTube cookie arguments for yt-dlp to bypass bot detection."""
|
|
|
|
|
+ logger = logging.getLogger(__name__)
|
|
|
|
|
+
|
|
|
|
|
+ cookies_file = "youtube_cookies.txt"
|
|
|
|
|
+ cookie_args = []
|
|
|
|
|
+
|
|
|
|
|
+ # Check if cookies file exists and is recent (less than 24 hours)
|
|
|
|
|
+ if os.path.exists(cookies_file):
|
|
|
|
|
+ file_age = time.time() - os.path.getmtime(cookies_file)
|
|
|
|
|
+ if file_age < 86400: # 24 hours
|
|
|
|
|
+ logger.debug(f"Using existing cookies file (age: {file_age/3600:.1f} hours)")
|
|
|
|
|
+ cookie_args = ["--cookies", cookies_file]
|
|
|
|
|
+ else:
|
|
|
|
|
+ logger.debug("Cookies file is old, will try to refresh")
|
|
|
|
|
+
|
|
|
|
|
+ # If no cookie file or it's old, try to setup fresh cookies
|
|
|
|
|
+ if not cookie_args:
|
|
|
|
|
+ try:
|
|
|
|
|
+ logger.debug("Setting up YouTube cookie authentication...")
|
|
|
|
|
+ fresh_cookies = setup_youtube_access()
|
|
|
|
|
+ if fresh_cookies and os.path.exists(fresh_cookies):
|
|
|
|
|
+ cookie_args = ["--cookies", fresh_cookies]
|
|
|
|
|
+ logger.debug("✅ Fresh cookies obtained")
|
|
|
|
|
+ else:
|
|
|
|
|
+ logger.debug("⚠️ Could not obtain fresh cookies")
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.debug(f"Cookie setup failed: {e}")
|
|
|
|
|
+
|
|
|
|
|
+ # Fallback: try browser extraction as last resort
|
|
|
|
|
+ if not cookie_args:
|
|
|
|
|
+ logger.debug("Trying browser cookie extraction as fallback...")
|
|
|
|
|
+ browser_options = ['chrome', 'firefox', 'edge']
|
|
|
|
|
+ for browser in browser_options:
|
|
|
|
|
+ try:
|
|
|
|
|
+ # Test if browser cookies work
|
|
|
|
|
+ test_cmd = ['yt-dlp', '--cookies-from-browser', browser, '--simulate', '--no-warnings', 'https://www.youtube.com/watch?v=dQw4w9WgXcQ']
|
|
|
|
|
+ result = subprocess.run(test_cmd, capture_output=True, text=True, timeout=15)
|
|
|
|
|
+ if result.returncode == 0:
|
|
|
|
|
+ cookie_args = ["--cookies-from-browser", browser]
|
|
|
|
|
+ logger.debug(f"✅ Using {browser} browser cookies")
|
|
|
|
|
+ break
|
|
|
|
|
+ except:
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ # Add additional anti-bot arguments
|
|
|
|
|
+ anti_bot_args = [
|
|
|
|
|
+ "--user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
|
|
|
+ "--sleep-interval", "1",
|
|
|
|
|
+ "--max-sleep-interval", "3",
|
|
|
|
|
+ "--retry-sleep", "linear=2:5",
|
|
|
|
|
+ "--fragment-retries", "3",
|
|
|
|
|
+ "--socket-timeout", "30"
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ return cookie_args + anti_bot_args
|
|
|
|
|
+
|
|
|
def authenticate_youtube_api():
|
|
def authenticate_youtube_api():
|
|
|
"""Authenticate using our enhanced auth system (tries non-interactive first)."""
|
|
"""Authenticate using our enhanced auth system (tries non-interactive first)."""
|
|
|
logger = logging.getLogger(__name__)
|
|
logger = logging.getLogger(__name__)
|
|
@@ -364,9 +421,13 @@ def list_available_formats(video_id):
|
|
|
logger = logging.getLogger(__name__)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
|
|
+ # Get cookie arguments for bot detection bypass
|
|
|
|
|
+ cookie_args = get_youtube_cookie_args()
|
|
|
|
|
+
|
|
|
command = [
|
|
command = [
|
|
|
"yt-dlp",
|
|
"yt-dlp",
|
|
|
"--list-formats",
|
|
"--list-formats",
|
|
|
|
|
+ ] + cookie_args + [
|
|
|
f"https://www.youtube.com/watch?v={video_id}"
|
|
f"https://www.youtube.com/watch?v={video_id}"
|
|
|
]
|
|
]
|
|
|
|
|
|
|
@@ -397,9 +458,13 @@ def download_video(video_id, output_path, video_title="Unknown"):
|
|
|
# First, check what format would be downloaded to determine compatibility
|
|
# First, check what format would be downloaded to determine compatibility
|
|
|
logger.debug(f"Checking video format compatibility for: {video_title}")
|
|
logger.debug(f"Checking video format compatibility for: {video_title}")
|
|
|
|
|
|
|
|
|
|
+ # Get cookie arguments for bot detection bypass
|
|
|
|
|
+ cookie_args = get_youtube_cookie_args()
|
|
|
|
|
+
|
|
|
simulate_command = [
|
|
simulate_command = [
|
|
|
"yt-dlp", "--simulate", "--print", "%(format_id)s|%(vcodec)s|%(height)s|%(ext)s",
|
|
"yt-dlp", "--simulate", "--print", "%(format_id)s|%(vcodec)s|%(height)s|%(ext)s",
|
|
|
"-f", "(bestvideo[height>=2160][vcodec~='^(avc|h264|hevc|h265)']+bestaudio)/(bestvideo[height>=2160]+bestaudio)/(bestvideo[height>=1440]+bestaudio)/(bestvideo[height>=1080]+bestaudio)/best",
|
|
"-f", "(bestvideo[height>=2160][vcodec~='^(avc|h264|hevc|h265)']+bestaudio)/(bestvideo[height>=2160]+bestaudio)/(bestvideo[height>=1440]+bestaudio)/(bestvideo[height>=1080]+bestaudio)/best",
|
|
|
|
|
+ ] + cookie_args + [
|
|
|
f"https://www.youtube.com/watch?v={video_id}"
|
|
f"https://www.youtube.com/watch?v={video_id}"
|
|
|
]
|
|
]
|
|
|
|
|
|
|
@@ -452,7 +517,7 @@ def download_video(video_id, output_path, video_title="Unknown"):
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
# 4K-prioritized approach: Try Apple TV compatible 4K first, then any 4K, then lower resolutions
|
|
# 4K-prioritized approach: Try Apple TV compatible 4K first, then any 4K, then lower resolutions
|
|
|
- command = [
|
|
|
|
|
|
|
+ base_command = [
|
|
|
"yt-dlp",
|
|
"yt-dlp",
|
|
|
"-f", "(bestvideo[height>=2160][vcodec~='^(avc|h264|hevc|h265)']+bestaudio)/(bestvideo[height>=2160]+bestaudio)/(bestvideo[height>=1440]+bestaudio)/(bestvideo[height>=1080]+bestaudio)/best",
|
|
"-f", "(bestvideo[height>=2160][vcodec~='^(avc|h264|hevc|h265)']+bestaudio)/(bestvideo[height>=2160]+bestaudio)/(bestvideo[height>=1440]+bestaudio)/(bestvideo[height>=1080]+bestaudio)/best",
|
|
|
"--merge-output-format", "mp4",
|
|
"--merge-output-format", "mp4",
|
|
@@ -460,6 +525,11 @@ def download_video(video_id, output_path, video_title="Unknown"):
|
|
|
"--newline", # Better for parsing progress
|
|
"--newline", # Better for parsing progress
|
|
|
"--verbose" if logger.isEnabledFor(logging.DEBUG) else "--no-warnings",
|
|
"--verbose" if logger.isEnabledFor(logging.DEBUG) else "--no-warnings",
|
|
|
"--write-info-json", # Write metadata for analysis
|
|
"--write-info-json", # Write metadata for analysis
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ # Add cookie authentication to bypass bot detection
|
|
|
|
|
+ cookie_args = get_youtube_cookie_args()
|
|
|
|
|
+ command = base_command + cookie_args + [
|
|
|
f"https://www.youtube.com/watch?v={video_id}",
|
|
f"https://www.youtube.com/watch?v={video_id}",
|
|
|
"-o", output_template
|
|
"-o", output_template
|
|
|
]
|
|
]
|
|
@@ -782,7 +852,7 @@ def sanitize_filename(filename):
|
|
|
return sanitized
|
|
return sanitized
|
|
|
|
|
|
|
|
def test_vaapi_support():
|
|
def test_vaapi_support():
|
|
|
- """Test if Intel VAAPI hardware acceleration is available."""
|
|
|
|
|
|
|
+ """Test if Intel VAAPI/QSV hardware acceleration is available for both encoding and decoding."""
|
|
|
logger = logging.getLogger(__name__)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
@@ -800,19 +870,76 @@ def test_vaapi_support():
|
|
|
result = subprocess.run(test_cmd, capture_output=True, text=True, timeout=10)
|
|
result = subprocess.run(test_cmd, capture_output=True, text=True, timeout=10)
|
|
|
hevc_vaapi_available = result.returncode == 0
|
|
hevc_vaapi_available = result.returncode == 0
|
|
|
|
|
|
|
|
- if h264_vaapi_available and hevc_vaapi_available:
|
|
|
|
|
- logger.info("🚀 Intel VAAPI hardware acceleration available (H.264 + HEVC)")
|
|
|
|
|
- return True, True
|
|
|
|
|
- elif h264_vaapi_available:
|
|
|
|
|
- logger.info("🚀 Intel VAAPI hardware acceleration available (H.264 only)")
|
|
|
|
|
- return True, False
|
|
|
|
|
|
|
+ # Test H.264 VAAPI decoding - create a small test file and try to decode it
|
|
|
|
|
+ h264_decode_available = False
|
|
|
|
|
+ try:
|
|
|
|
|
+ # Create a small test H.264 file
|
|
|
|
|
+ create_test_cmd = [FFMPEG_PATH, "-hide_banner", "-f", "lavfi", "-i", "testsrc=duration=1:size=320x240:rate=1",
|
|
|
|
|
+ "-c:v", "libx264", "-frames:v", "5", "-f", "mp4", "/tmp/vaapi_test.mp4"]
|
|
|
|
|
+ subprocess.run(create_test_cmd, capture_output=True, text=True, timeout=10, check=True)
|
|
|
|
|
+
|
|
|
|
|
+ # Test hardware decoding
|
|
|
|
|
+ decode_test_cmd = [FFMPEG_PATH, "-hide_banner", "-hwaccel", "vaapi", "-hwaccel_device", "/dev/dri/renderD128",
|
|
|
|
|
+ "-i", "/tmp/vaapi_test.mp4", "-f", "null", "-"]
|
|
|
|
|
+ result = subprocess.run(decode_test_cmd, capture_output=True, text=True, timeout=10)
|
|
|
|
|
+ h264_decode_available = result.returncode == 0
|
|
|
|
|
+
|
|
|
|
|
+ # Clean up test file
|
|
|
|
|
+ try:
|
|
|
|
|
+ os.remove("/tmp/vaapi_test.mp4")
|
|
|
|
|
+ except:
|
|
|
|
|
+ pass
|
|
|
|
|
+ except:
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+ # Test VP9 VAAPI decoding capability (Intel HD Graphics 630 supports VP9 via VAAPI)
|
|
|
|
|
+ vp9_vaapi_decode_available = False
|
|
|
|
|
+ try:
|
|
|
|
|
+ # Create a small VP9 test file
|
|
|
|
|
+ create_vp9_cmd = [FFMPEG_PATH, "-hide_banner", "-f", "lavfi", "-i", "testsrc=duration=1:size=320x240:rate=1",
|
|
|
|
|
+ "-c:v", "libvpx-vp9", "-frames:v", "5", "-f", "webm", "/tmp/vp9_test.webm"]
|
|
|
|
|
+ result = subprocess.run(create_vp9_cmd, capture_output=True, text=True, timeout=15)
|
|
|
|
|
+
|
|
|
|
|
+ if result.returncode == 0:
|
|
|
|
|
+ # Test VP9 hardware decoding with VAAPI
|
|
|
|
|
+ decode_vp9_cmd = [FFMPEG_PATH, "-hide_banner", "-hwaccel", "vaapi", "-hwaccel_device", "/dev/dri/renderD128",
|
|
|
|
|
+ "-i", "/tmp/vp9_test.webm", "-f", "null", "-"]
|
|
|
|
|
+ result = subprocess.run(decode_vp9_cmd, capture_output=True, text=True, timeout=10)
|
|
|
|
|
+ vp9_vaapi_decode_available = result.returncode == 0
|
|
|
|
|
+
|
|
|
|
|
+ # Clean up test file
|
|
|
|
|
+ try:
|
|
|
|
|
+ os.remove("/tmp/vp9_test.webm")
|
|
|
|
|
+ except:
|
|
|
|
|
+ pass
|
|
|
|
|
+ except:
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+ # AV1 is not supported on Intel HD Graphics 630, so we'll skip it
|
|
|
|
|
+ av1_decode_available = False
|
|
|
|
|
+
|
|
|
|
|
+ # Log results with comprehensive hardware support info
|
|
|
|
|
+ hw_support = []
|
|
|
|
|
+ if h264_vaapi_available:
|
|
|
|
|
+ hw_support.append("H.264 encode")
|
|
|
|
|
+ if hevc_vaapi_available:
|
|
|
|
|
+ hw_support.append("HEVC encode")
|
|
|
|
|
+ if h264_decode_available:
|
|
|
|
|
+ hw_support.append("H.264 decode")
|
|
|
|
|
+ if vp9_vaapi_decode_available:
|
|
|
|
|
+ hw_support.append("VP9 decode")
|
|
|
|
|
+
|
|
|
|
|
+ if hw_support:
|
|
|
|
|
+ logger.info(f"🚀 Intel hardware acceleration available: {', '.join(hw_support)}")
|
|
|
else:
|
|
else:
|
|
|
- logger.info("⚠️ Intel VAAPI hardware acceleration not available, falling back to software encoding")
|
|
|
|
|
- return False, False
|
|
|
|
|
|
|
+ logger.info("⚠️ Intel hardware acceleration not available, falling back to software encoding")
|
|
|
|
|
+
|
|
|
|
|
+ return (h264_vaapi_available, hevc_vaapi_available, h264_decode_available,
|
|
|
|
|
+ av1_decode_available, vp9_vaapi_decode_available)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
- logger.warning(f"Could not test VAAPI support: {e}")
|
|
|
|
|
- return False, False
|
|
|
|
|
|
|
+ logger.warning(f"Could not test hardware acceleration support: {e}")
|
|
|
|
|
+ return False, False, False, False, False
|
|
|
|
|
|
|
|
def convert_to_apple_tv_compatible(input_file):
|
|
def convert_to_apple_tv_compatible(input_file):
|
|
|
"""Convert a video to Apple TV 4K compatible format with Intel VAAPI hardware acceleration."""
|
|
"""Convert a video to Apple TV 4K compatible format with Intel VAAPI hardware acceleration."""
|
|
@@ -840,7 +967,7 @@ def convert_to_apple_tv_compatible(input_file):
|
|
|
logger.debug(f"Output file: {mp4_file}")
|
|
logger.debug(f"Output file: {mp4_file}")
|
|
|
|
|
|
|
|
# Test VAAPI support
|
|
# Test VAAPI support
|
|
|
- vaapi_h264_available, vaapi_hevc_available = test_vaapi_support()
|
|
|
|
|
|
|
+ vaapi_h264_available, vaapi_hevc_available, vaapi_decode_available, av1_decode_available, vp9_vaapi_decode_available = test_vaapi_support()
|
|
|
|
|
|
|
|
# Update Home Assistant
|
|
# Update Home Assistant
|
|
|
update_homeassistant_stats(
|
|
update_homeassistant_stats(
|
|
@@ -863,6 +990,7 @@ def convert_to_apple_tv_compatible(input_file):
|
|
|
video_stream = next((s for s in video_info['streams'] if s['codec_type'] == 'video'), None)
|
|
video_stream = next((s for s in video_info['streams'] if s['codec_type'] == 'video'), None)
|
|
|
is_4k = video_stream and int(video_stream.get('height', 0)) >= 2160
|
|
is_4k = video_stream and int(video_stream.get('height', 0)) >= 2160
|
|
|
is_high_res = video_stream and int(video_stream.get('height', 0)) >= 1080
|
|
is_high_res = video_stream and int(video_stream.get('height', 0)) >= 1080
|
|
|
|
|
+ input_codec = video_stream.get('codec_name', 'unknown').lower() if video_stream else 'unknown'
|
|
|
|
|
|
|
|
if video_stream:
|
|
if video_stream:
|
|
|
height = video_stream.get('height', 'unknown')
|
|
height = video_stream.get('height', 'unknown')
|
|
@@ -874,13 +1002,36 @@ def convert_to_apple_tv_compatible(input_file):
|
|
|
logger.warning(f"Could not probe video properties: {e}, using default settings")
|
|
logger.warning(f"Could not probe video properties: {e}, using default settings")
|
|
|
is_4k = False
|
|
is_4k = False
|
|
|
is_high_res = True
|
|
is_high_res = True
|
|
|
|
|
+ input_codec = 'unknown'
|
|
|
|
|
+
|
|
|
|
|
+ # Intel VAAPI optimized encoding with hardware decoding based on source quality and hardware support:
|
|
|
|
|
+ hwaccel_args = []
|
|
|
|
|
+ filter_args = []
|
|
|
|
|
+
|
|
|
|
|
+ # Determine optimal hardware decoding strategy based on input codec
|
|
|
|
|
+ if input_codec == 'av1' and av1_decode_available:
|
|
|
|
|
+ # AV1 is not supported on Intel HD Graphics 630, fallback to software
|
|
|
|
|
+ filter_args = ["-vaapi_device", "/dev/dri/renderD128", "-vf", "format=nv12,hwupload"]
|
|
|
|
|
+ logger.info(f"Using software decoding with GPU upload for AV1 input (no hardware AV1 support)")
|
|
|
|
|
+ elif input_codec in ['vp9', 'vp09'] and vp9_vaapi_decode_available:
|
|
|
|
|
+ # Use Intel VAAPI for VP9 hardware decoding
|
|
|
|
|
+ hwaccel_args = ["-hwaccel", "vaapi", "-hwaccel_device", "/dev/dri/renderD128"]
|
|
|
|
|
+ filter_args = ["-vaapi_device", "/dev/dri/renderD128", "-vf", "format=nv12,hwupload"]
|
|
|
|
|
+ logger.info(f"🚀 Using Intel VAAPI hardware decoding for VP9 input")
|
|
|
|
|
+ elif input_codec in ['h264', 'avc'] and vaapi_decode_available:
|
|
|
|
|
+ # Use Intel VAAPI for H.264 hardware decoding (decode + encode on same device)
|
|
|
|
|
+ hwaccel_args = ["-hwaccel", "vaapi", "-hwaccel_device", "/dev/dri/renderD128"]
|
|
|
|
|
+ filter_args = ["-vaapi_device", "/dev/dri/renderD128", "-vf", "format=nv12,hwupload"]
|
|
|
|
|
+ logger.info(f"🚀 Using Intel VAAPI hardware decoding for H.264 input")
|
|
|
|
|
+ else:
|
|
|
|
|
+ # Fallback: CPU decode with GPU upload for compatibility
|
|
|
|
|
+ filter_args = ["-vaapi_device", "/dev/dri/renderD128", "-vf", "format=nv12,hwupload"]
|
|
|
|
|
+ logger.info(f"Using software decoding with GPU upload for {input_codec} input")
|
|
|
|
|
|
|
|
- # Intel VAAPI optimized encoding based on source quality and hardware support:
|
|
|
|
|
if is_4k and vaapi_hevc_available:
|
|
if is_4k and vaapi_hevc_available:
|
|
|
# 4K content with VAAPI HEVC: Use hardware-accelerated H.265
|
|
# 4K content with VAAPI HEVC: Use hardware-accelerated H.265
|
|
|
codec_args = [
|
|
codec_args = [
|
|
|
- "-vaapi_device", "/dev/dri/renderD128", # VAAPI device
|
|
|
|
|
- "-vf", "format=nv12,hwupload", # Upload to GPU memory
|
|
|
|
|
|
|
+ *filter_args,
|
|
|
"-c:v", "hevc_vaapi", # Intel VAAPI HEVC encoder
|
|
"-c:v", "hevc_vaapi", # Intel VAAPI HEVC encoder
|
|
|
"-qp", "20", # Quality parameter (lower = better)
|
|
"-qp", "20", # Quality parameter (lower = better)
|
|
|
"-rc_mode", "CQP", # Constant Quality mode
|
|
"-rc_mode", "CQP", # Constant Quality mode
|
|
@@ -890,8 +1041,7 @@ def convert_to_apple_tv_compatible(input_file):
|
|
|
# 4K/1080p content with VAAPI H.264: Use hardware-accelerated H.264
|
|
# 4K/1080p content with VAAPI H.264: Use hardware-accelerated H.264
|
|
|
quality = "20" if is_4k else "22" # Slightly lower quality for 1080p
|
|
quality = "20" if is_4k else "22" # Slightly lower quality for 1080p
|
|
|
codec_args = [
|
|
codec_args = [
|
|
|
- "-vaapi_device", "/dev/dri/renderD128", # VAAPI device
|
|
|
|
|
- "-vf", "format=nv12,hwupload", # Upload to GPU memory
|
|
|
|
|
|
|
+ *filter_args,
|
|
|
"-c:v", "h264_vaapi", # Intel VAAPI H.264 encoder
|
|
"-c:v", "h264_vaapi", # Intel VAAPI H.264 encoder
|
|
|
"-qp", quality, # Quality parameter
|
|
"-qp", quality, # Quality parameter
|
|
|
"-rc_mode", "CQP", # Constant Quality mode
|
|
"-rc_mode", "CQP", # Constant Quality mode
|
|
@@ -908,6 +1058,7 @@ def convert_to_apple_tv_compatible(input_file):
|
|
|
"-level", "5.1", # Level 5.1 supports 4K
|
|
"-level", "5.1", # Level 5.1 supports 4K
|
|
|
"-pix_fmt", "yuv420p", # Standard compatibility
|
|
"-pix_fmt", "yuv420p", # Standard compatibility
|
|
|
]
|
|
]
|
|
|
|
|
+ hwaccel_args = [] # No hardware acceleration
|
|
|
logger.info("⚠️ Using software H.265 encoding for 4K content (VAAPI not available)")
|
|
logger.info("⚠️ Using software H.265 encoding for 4K content (VAAPI not available)")
|
|
|
elif is_high_res:
|
|
elif is_high_res:
|
|
|
# 1080p content without VAAPI: Use software H.264
|
|
# 1080p content without VAAPI: Use software H.264
|
|
@@ -918,6 +1069,7 @@ def convert_to_apple_tv_compatible(input_file):
|
|
|
"-crf", "21", # Good quality for 1080p
|
|
"-crf", "21", # Good quality for 1080p
|
|
|
"-level", "4.1", # Level 4.1 supports 1080p
|
|
"-level", "4.1", # Level 4.1 supports 1080p
|
|
|
]
|
|
]
|
|
|
|
|
+ hwaccel_args = [] # No hardware acceleration
|
|
|
logger.info("⚠️ Using software H.264 encoding for 1080p content (VAAPI not available)")
|
|
logger.info("⚠️ Using software H.264 encoding for 1080p content (VAAPI not available)")
|
|
|
else:
|
|
else:
|
|
|
# Lower resolution: Use software H.264 with faster settings
|
|
# Lower resolution: Use software H.264 with faster settings
|
|
@@ -928,17 +1080,20 @@ def convert_to_apple_tv_compatible(input_file):
|
|
|
"-crf", "23", # Standard quality
|
|
"-crf", "23", # Standard quality
|
|
|
"-level", "3.1", # Level 3.1 for lower res
|
|
"-level", "3.1", # Level 3.1 for lower res
|
|
|
]
|
|
]
|
|
|
|
|
+ hwaccel_args = [] # No hardware acceleration
|
|
|
logger.info("Using software H.264 encoding for lower resolution content")
|
|
logger.info("Using software H.264 encoding for lower resolution content")
|
|
|
|
|
|
|
|
# Add progress reporting to FFmpeg command
|
|
# Add progress reporting to FFmpeg command
|
|
|
command = [
|
|
command = [
|
|
|
- FFMPEG_PATH, "-i", input_file,
|
|
|
|
|
- *codec_args,
|
|
|
|
|
- "-c:a", "aac", # AAC audio codec
|
|
|
|
|
- "-b:a", "192k", # Higher audio bitrate for better quality
|
|
|
|
|
- "-movflags", "+faststart", # Optimize for streaming/quick playback
|
|
|
|
|
- "-progress", "pipe:1", # Output progress to stdout
|
|
|
|
|
- "-y", # Overwrite output file if exists
|
|
|
|
|
|
|
+ FFMPEG_PATH,
|
|
|
|
|
+ *hwaccel_args, # Hardware acceleration args (decode)
|
|
|
|
|
+ "-i", input_file,
|
|
|
|
|
+ *codec_args, # Video codec args (encode + filters)
|
|
|
|
|
+ "-c:a", "aac", # AAC audio codec
|
|
|
|
|
+ "-b:a", "192k", # Higher audio bitrate for better quality
|
|
|
|
|
+ "-movflags", "+faststart", # Optimize for streaming/quick playback
|
|
|
|
|
+ "-progress", "pipe:1", # Output progress to stdout
|
|
|
|
|
+ "-y", # Overwrite output file if exists
|
|
|
mp4_file
|
|
mp4_file
|
|
|
]
|
|
]
|
|
|
|
|
|
|
@@ -1095,7 +1250,7 @@ def convert_to_apple_tv_compatible_with_target(input_file, target_file):
|
|
|
temp_output = f"{base_name}.converting{ext}"
|
|
temp_output = f"{base_name}.converting{ext}"
|
|
|
|
|
|
|
|
# Test VAAPI support
|
|
# Test VAAPI support
|
|
|
- vaapi_h264_available, vaapi_hevc_available = test_vaapi_support()
|
|
|
|
|
|
|
+ vaapi_h264_available, vaapi_hevc_available, vaapi_decode_available, av1_decode_available, vp9_vaapi_decode_available = test_vaapi_support()
|
|
|
|
|
|
|
|
# Update Home Assistant
|
|
# Update Home Assistant
|
|
|
update_homeassistant_stats(
|
|
update_homeassistant_stats(
|
|
@@ -1118,6 +1273,7 @@ def convert_to_apple_tv_compatible_with_target(input_file, target_file):
|
|
|
video_stream = next((s for s in video_info['streams'] if s['codec_type'] == 'video'), None)
|
|
video_stream = next((s for s in video_info['streams'] if s['codec_type'] == 'video'), None)
|
|
|
is_4k = video_stream and int(video_stream.get('height', 0)) >= 2160
|
|
is_4k = video_stream and int(video_stream.get('height', 0)) >= 2160
|
|
|
is_high_res = video_stream and int(video_stream.get('height', 0)) >= 1080
|
|
is_high_res = video_stream and int(video_stream.get('height', 0)) >= 1080
|
|
|
|
|
+ input_codec = video_stream.get('codec_name', 'unknown').lower() if video_stream else 'unknown'
|
|
|
|
|
|
|
|
if video_stream:
|
|
if video_stream:
|
|
|
height = video_stream.get('height', 'unknown')
|
|
height = video_stream.get('height', 'unknown')
|
|
@@ -1129,13 +1285,36 @@ def convert_to_apple_tv_compatible_with_target(input_file, target_file):
|
|
|
logger.warning(f"Could not probe video properties: {e}, using default settings")
|
|
logger.warning(f"Could not probe video properties: {e}, using default settings")
|
|
|
is_4k = False
|
|
is_4k = False
|
|
|
is_high_res = True
|
|
is_high_res = True
|
|
|
|
|
+ input_codec = 'unknown'
|
|
|
|
|
+
|
|
|
|
|
+ # Intel VAAPI optimized encoding with hardware decoding based on source quality and hardware support
|
|
|
|
|
+ hwaccel_args = []
|
|
|
|
|
+ filter_args = []
|
|
|
|
|
+
|
|
|
|
|
+ # Determine optimal hardware decoding strategy based on input codec
|
|
|
|
|
+ if input_codec == 'av1' and av1_decode_available:
|
|
|
|
|
+ # AV1 is not supported on Intel HD Graphics 630, fallback to software
|
|
|
|
|
+ filter_args = ["-vaapi_device", "/dev/dri/renderD128", "-vf", "format=nv12,hwupload"]
|
|
|
|
|
+ logger.info(f"Using software decoding with GPU upload for AV1 input (no hardware AV1 support)")
|
|
|
|
|
+ elif input_codec in ['vp9', 'vp09'] and vp9_vaapi_decode_available:
|
|
|
|
|
+ # Use Intel VAAPI for VP9 hardware decoding
|
|
|
|
|
+ hwaccel_args = ["-hwaccel", "vaapi", "-hwaccel_device", "/dev/dri/renderD128"]
|
|
|
|
|
+ filter_args = ["-vaapi_device", "/dev/dri/renderD128", "-vf", "format=nv12,hwupload"]
|
|
|
|
|
+ logger.info(f"🚀 Using Intel VAAPI hardware decoding for VP9 input")
|
|
|
|
|
+ elif input_codec in ['h264', 'avc'] and vaapi_decode_available:
|
|
|
|
|
+ # Use Intel VAAPI for H.264 hardware decoding (decode + encode on same device)
|
|
|
|
|
+ hwaccel_args = ["-hwaccel", "vaapi", "-hwaccel_device", "/dev/dri/renderD128"]
|
|
|
|
|
+ filter_args = ["-vaapi_device", "/dev/dri/renderD128", "-vf", "format=nv12,hwupload"]
|
|
|
|
|
+ logger.info(f"🚀 Using Intel VAAPI hardware decoding for H.264 input")
|
|
|
|
|
+ else:
|
|
|
|
|
+ # Fallback: CPU decode with GPU upload for compatibility
|
|
|
|
|
+ filter_args = ["-vaapi_device", "/dev/dri/renderD128", "-vf", "format=nv12,hwupload"]
|
|
|
|
|
+ logger.info(f"Using software decoding with GPU upload for {input_codec} input")
|
|
|
|
|
|
|
|
- # Intel VAAPI optimized encoding based on source quality and hardware support
|
|
|
|
|
if is_4k and vaapi_hevc_available:
|
|
if is_4k and vaapi_hevc_available:
|
|
|
# 4K content with VAAPI HEVC: Use hardware-accelerated H.265
|
|
# 4K content with VAAPI HEVC: Use hardware-accelerated H.265
|
|
|
codec_args = [
|
|
codec_args = [
|
|
|
- "-vaapi_device", "/dev/dri/renderD128", # VAAPI device
|
|
|
|
|
- "-vf", "format=nv12,hwupload", # Upload to GPU memory
|
|
|
|
|
|
|
+ *filter_args,
|
|
|
"-c:v", "hevc_vaapi", # Intel VAAPI HEVC encoder
|
|
"-c:v", "hevc_vaapi", # Intel VAAPI HEVC encoder
|
|
|
"-qp", "20", # Quality parameter (lower = better)
|
|
"-qp", "20", # Quality parameter (lower = better)
|
|
|
"-rc_mode", "CQP", # Constant Quality mode
|
|
"-rc_mode", "CQP", # Constant Quality mode
|
|
@@ -1145,8 +1324,7 @@ def convert_to_apple_tv_compatible_with_target(input_file, target_file):
|
|
|
# 4K/1080p content with VAAPI H.264: Use hardware-accelerated H.264
|
|
# 4K/1080p content with VAAPI H.264: Use hardware-accelerated H.264
|
|
|
quality = "20" if is_4k else "22" # Slightly lower quality for 1080p
|
|
quality = "20" if is_4k else "22" # Slightly lower quality for 1080p
|
|
|
codec_args = [
|
|
codec_args = [
|
|
|
- "-vaapi_device", "/dev/dri/renderD128", # VAAPI device
|
|
|
|
|
- "-vf", "format=nv12,hwupload", # Upload to GPU memory
|
|
|
|
|
|
|
+ *filter_args,
|
|
|
"-c:v", "h264_vaapi", # Intel VAAPI H.264 encoder
|
|
"-c:v", "h264_vaapi", # Intel VAAPI H.264 encoder
|
|
|
"-qp", quality, # Quality parameter
|
|
"-qp", quality, # Quality parameter
|
|
|
"-rc_mode", "CQP", # Constant Quality mode
|
|
"-rc_mode", "CQP", # Constant Quality mode
|
|
@@ -1163,6 +1341,7 @@ def convert_to_apple_tv_compatible_with_target(input_file, target_file):
|
|
|
"-level", "5.1", # Level 5.1 supports 4K
|
|
"-level", "5.1", # Level 5.1 supports 4K
|
|
|
"-pix_fmt", "yuv420p", # Standard compatibility
|
|
"-pix_fmt", "yuv420p", # Standard compatibility
|
|
|
]
|
|
]
|
|
|
|
|
+ hwaccel_args = [] # No hardware acceleration
|
|
|
logger.info("⚠️ Using software H.265 encoding for 4K content (VAAPI not available)")
|
|
logger.info("⚠️ Using software H.265 encoding for 4K content (VAAPI not available)")
|
|
|
elif is_high_res:
|
|
elif is_high_res:
|
|
|
# 1080p content without VAAPI: Use software H.264
|
|
# 1080p content without VAAPI: Use software H.264
|
|
@@ -1173,6 +1352,7 @@ def convert_to_apple_tv_compatible_with_target(input_file, target_file):
|
|
|
"-crf", "21", # Good quality for 1080p
|
|
"-crf", "21", # Good quality for 1080p
|
|
|
"-level", "4.1", # Level 4.1 supports 1080p
|
|
"-level", "4.1", # Level 4.1 supports 1080p
|
|
|
]
|
|
]
|
|
|
|
|
+ hwaccel_args = [] # No hardware acceleration
|
|
|
logger.info("⚠️ Using software H.264 encoding for 1080p content (VAAPI not available)")
|
|
logger.info("⚠️ Using software H.264 encoding for 1080p content (VAAPI not available)")
|
|
|
else:
|
|
else:
|
|
|
# Lower resolution: Use software H.264 with faster settings
|
|
# Lower resolution: Use software H.264 with faster settings
|
|
@@ -1183,17 +1363,20 @@ def convert_to_apple_tv_compatible_with_target(input_file, target_file):
|
|
|
"-crf", "23", # Standard quality
|
|
"-crf", "23", # Standard quality
|
|
|
"-level", "3.1", # Level 3.1 for lower res
|
|
"-level", "3.1", # Level 3.1 for lower res
|
|
|
]
|
|
]
|
|
|
|
|
+ hwaccel_args = [] # No hardware acceleration
|
|
|
logger.info("Using software H.264 encoding for lower resolution content")
|
|
logger.info("Using software H.264 encoding for lower resolution content")
|
|
|
|
|
|
|
|
# Add progress reporting to FFmpeg command
|
|
# Add progress reporting to FFmpeg command
|
|
|
command = [
|
|
command = [
|
|
|
- FFMPEG_PATH, "-i", input_file,
|
|
|
|
|
- *codec_args,
|
|
|
|
|
- "-c:a", "aac", # AAC audio codec
|
|
|
|
|
- "-b:a", "192k", # Higher audio bitrate for better quality
|
|
|
|
|
- "-movflags", "+faststart", # Optimize for streaming/quick playback
|
|
|
|
|
- "-progress", "pipe:1", # Output progress to stdout
|
|
|
|
|
- "-y", # Overwrite output file if exists
|
|
|
|
|
|
|
+ FFMPEG_PATH,
|
|
|
|
|
+ *hwaccel_args, # Hardware acceleration args (decode)
|
|
|
|
|
+ "-i", input_file,
|
|
|
|
|
+ *codec_args, # Video codec args (encode + filters)
|
|
|
|
|
+ "-c:a", "aac", # AAC audio codec
|
|
|
|
|
+ "-b:a", "192k", # Higher audio bitrate for better quality
|
|
|
|
|
+ "-movflags", "+faststart", # Optimize for streaming/quick playback
|
|
|
|
|
+ "-progress", "pipe:1", # Output progress to stdout
|
|
|
|
|
+ "-y", # Overwrite output file if exists
|
|
|
temp_output
|
|
temp_output
|
|
|
]
|
|
]
|
|
|
|
|
|