Explorar el Código

conversion bugfix

Murad Jabir hace 1 año
padre
commit
e0faa8ddd8
Se han modificado 1 ficheros con 169 adiciones y 36 borrados
  1. 169 36
      main.py

+ 169 - 36
main.py

@@ -1,7 +1,6 @@
 import os
 import time
-import datetime
-from datetime import datetime, time as dt_time
+from datetime import datetime, time as dt_time, timedelta
 import pickle
 import threading
 import queue
@@ -204,6 +203,7 @@ FFMPEG_PATH = "/usr/bin/ffmpeg"
 # Global conversion queue and executor
 conversion_queue = queue.Queue(maxsize=CONVERSION_QUEUE_SIZE)
 conversion_executor = ThreadPoolExecutor(max_workers=MAX_CONCURRENT_CONVERSIONS, thread_name_prefix="VideoConverter")
+conversion_worker_stop = False  # Flag to signal conversion worker to stop
 
 # Progress tracking
 download_stats = {
@@ -294,7 +294,7 @@ def get_recent_videos(youtube, channel_id):
     """Fetch videos from a channel published in the last `DAYS_BACK` days."""
     logger = logging.getLogger(__name__)
     recent_videos = []
-    published_after = (datetime.datetime.now() - datetime.timedelta(days=DAYS_BACK)).isoformat("T") + "Z"
+    published_after = (datetime.now() - timedelta(days=DAYS_BACK)).isoformat("T") + "Z"
     
     try:
         request = youtube.search().list(
@@ -718,13 +718,14 @@ def print_queue_status():
 
 def process_conversion_queue():
     """Background worker to process the conversion queue."""
+    global conversion_worker_stop
     logger = logging.getLogger(__name__)
     logger.info("Conversion queue worker started")
     
-    while True:
+    while not conversion_worker_stop:
         try:
             # Get item from queue (blocks until available)
-            queue_item = conversion_queue.get(timeout=30)
+            queue_item = conversion_queue.get(timeout=5)  # Reduced timeout to check stop flag more often
             
             # Handle both old format (priority, input_file) and new format (priority, input_file, target_file)
             if len(queue_item) == 2:
@@ -761,7 +762,9 @@ def process_conversion_queue():
             conversion_queue.task_done()
             
         except queue.Empty:
-            # No items in queue for 30 seconds, continue waiting
+            # No items in queue for 5 seconds, check if we should stop
+            if conversion_worker_stop:
+                break
             logger.debug("Conversion queue empty, waiting for new items...")
             continue
         except Exception as e:
@@ -770,6 +773,8 @@ def process_conversion_queue():
                 conversion_queue.task_done()
             except:
                 pass
+    
+    logger.info("Conversion queue worker stopped")
 
 def sanitize_filename(filename):
     """Sanitize a filename to remove invalid characters."""
@@ -781,7 +786,7 @@ def sanitize_filename(filename):
     return sanitized
 
 def test_vaapi_support():
-    """Test if Intel VAAPI hardware acceleration is available."""
+    """Test if Intel VAAPI hardware acceleration is available for encoding and decoding."""
     logger = logging.getLogger(__name__)
     
     try:
@@ -799,19 +804,72 @@ def test_vaapi_support():
         result = subprocess.run(test_cmd, capture_output=True, text=True, timeout=10)
         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 VP9 VAAPI decoding capability - create a test VP9 file and try hardware decoding
+        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
+        
+        # Test H.264 VAAPI decoding - create a test file and try to decode it
+        h264_vaapi_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/h264_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/h264_test.mp4", "-f", "null", "-"]
+            result = subprocess.run(decode_test_cmd, capture_output=True, text=True, timeout=10)
+            h264_vaapi_decode_available = result.returncode == 0
+            
+            # Clean up test file
+            try:
+                os.remove("/tmp/h264_test.mp4")
+            except:
+                pass
+        except:
+            pass
+        
+        # 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_vaapi_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 VAAPI hardware acceleration available: {', '.join(hw_support)}")
         else:
-            logger.info("⚠️ Intel VAAPI hardware acceleration not available, falling back to software encoding")
-            return False, False
+            logger.info("⚠️ Intel VAAPI hardware acceleration not available, falling back to software")
+            
+        return (h264_vaapi_available, hevc_vaapi_available, h264_vaapi_decode_available, vp9_vaapi_decode_available)
             
     except Exception as e:
         logger.warning(f"Could not test VAAPI support: {e}")
-        return False, False
+        return False, False, False, False
 
 def convert_to_apple_tv_compatible(input_file):
     """Convert a video to Apple TV 4K compatible format with Intel VAAPI hardware acceleration."""
@@ -839,7 +897,7 @@ def convert_to_apple_tv_compatible(input_file):
     logger.debug(f"Output file: {mp4_file}")
     
     # Test VAAPI support
-    vaapi_h264_available, vaapi_hevc_available = test_vaapi_support()
+    h264_encode, hevc_encode, h264_decode, vp9_decode = test_vaapi_support()
     
     # Update Home Assistant
     update_homeassistant_stats(
@@ -862,6 +920,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)
         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
+        input_codec = video_stream.get('codec_name', 'unknown').lower() if video_stream else 'unknown'
         
         if video_stream:
             height = video_stream.get('height', 'unknown')
@@ -873,24 +932,41 @@ def convert_to_apple_tv_compatible(input_file):
         logger.warning(f"Could not probe video properties: {e}, using default settings")
         is_4k = False
         is_high_res = True
+        input_codec = 'unknown'
+    
+    # Determine optimal hardware decoding strategy based on input codec
+    hwaccel_args = []
+    filter_args = []
+    
+    if input_codec in ['vp9', 'vp09'] and vp9_decode:
+        # 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 h264_decode:
+        # 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 encoding 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 hevc_encode:
         # 4K content with VAAPI HEVC: Use hardware-accelerated H.265
         codec_args = [
-            "-vaapi_device", "/dev/dri/renderD128",  # VAAPI device
-            "-vf", "format=nv12,hwupload",          # Upload to GPU memory
             "-c:v", "hevc_vaapi",                   # Intel VAAPI HEVC encoder
             "-qp", "20",                            # Quality parameter (lower = better)
             "-rc_mode", "CQP",                      # Constant Quality mode
         ]
         logger.info("🚀 Using Intel VAAPI HEVC hardware acceleration for 4K content")
-    elif (is_4k or is_high_res) and vaapi_h264_available:
+    elif (is_4k or is_high_res) and h264_encode:
         # 4K/1080p content with VAAPI H.264: Use hardware-accelerated H.264
         quality = "20" if is_4k else "22"  # Slightly lower quality for 1080p
         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
             "-qp", quality,                         # Quality parameter
             "-rc_mode", "CQP",                      # Constant Quality mode
@@ -931,8 +1007,11 @@ def convert_to_apple_tv_compatible(input_file):
     
     # Add progress reporting to FFmpeg command
     command = [
-        FFMPEG_PATH, "-i", input_file,
-        *codec_args,
+        FFMPEG_PATH,
+        *hwaccel_args,              # Hardware decode arguments (if any)
+        "-i", input_file,
+        *filter_args,               # Video filter arguments (if any)
+        *codec_args,                # Video codec args (encode)
         "-c:a", "aac",              # AAC audio codec
         "-b:a", "192k",             # Higher audio bitrate for better quality
         "-movflags", "+faststart",  # Optimize for streaming/quick playback
@@ -1094,7 +1173,7 @@ def convert_to_apple_tv_compatible_with_target(input_file, target_file):
     temp_output = f"{base_name}.converting{ext}"
     
     # Test VAAPI support
-    vaapi_h264_available, vaapi_hevc_available = test_vaapi_support()
+    h264_encode, hevc_encode, h264_decode, vp9_decode = test_vaapi_support()
     
     # Update Home Assistant
     update_homeassistant_stats(
@@ -1117,6 +1196,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)
         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
+        input_codec = video_stream.get('codec_name', 'unknown').lower() if video_stream else 'unknown'
         
         if video_stream:
             height = video_stream.get('height', 'unknown')
@@ -1128,24 +1208,40 @@ def convert_to_apple_tv_compatible_with_target(input_file, target_file):
         logger.warning(f"Could not probe video properties: {e}, using default settings")
         is_4k = False
         is_high_res = True
+        input_codec = 'unknown'
+    
+    # Determine optimal hardware decoding strategy based on input codec
+    hwaccel_args = []
+    filter_args = []
+    
+    if input_codec in ['vp9', 'vp09'] and vp9_decode:
+        # 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 h264_decode:
+        # 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 encoding 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 hevc_encode:
         # 4K content with VAAPI HEVC: Use hardware-accelerated H.265
         codec_args = [
-            "-vaapi_device", "/dev/dri/renderD128",  # VAAPI device
-            "-vf", "format=nv12,hwupload",          # Upload to GPU memory
             "-c:v", "hevc_vaapi",                   # Intel VAAPI HEVC encoder
             "-qp", "20",                            # Quality parameter (lower = better)
             "-rc_mode", "CQP",                      # Constant Quality mode
         ]
         logger.info("🚀 Using Intel VAAPI HEVC hardware acceleration for 4K content")
-    elif (is_4k or is_high_res) and vaapi_h264_available:
+    elif (is_4k or is_high_res) and h264_encode:
         # 4K/1080p content with VAAPI H.264: Use hardware-accelerated H.264
         quality = "20" if is_4k else "22"  # Slightly lower quality for 1080p
         codec_args = [
-            "-vaapi_device", "/dev/dri/renderD128",  # VAAPI device
-            "-vf", "format=nv12,hwupload",          # Upload to GPU memory
             "-c:v", "h264_vaapi",                   # Intel VAAPI H.264 encoder
             "-qp", quality,                         # Quality parameter
             "-rc_mode", "CQP",                      # Constant Quality mode
@@ -1186,7 +1282,10 @@ def convert_to_apple_tv_compatible_with_target(input_file, target_file):
     
     # Add progress reporting to FFmpeg command
     command = [
-        FFMPEG_PATH, "-i", input_file,
+        FFMPEG_PATH, 
+        *hwaccel_args,              # Hardware decode arguments (if any)
+        "-i", input_file,
+        *filter_args,               # Video filter arguments (if any)
         *codec_args,
         "-c:a", "aac",              # AAC audio codec
         "-b:a", "192k",             # Higher audio bitrate for better quality
@@ -1435,6 +1534,29 @@ def check_exit_time():
         print("Current time exceeds exit time. Terminating script.")
         sys.exit(0)
 
+def cleanup_conversion_worker():
+    """Clean up the conversion worker thread and wait for pending conversions."""
+    global conversion_worker_stop
+    logger = logging.getLogger(__name__)
+    
+    logger.info("Cleaning up conversion worker...")
+    
+    # Signal the worker to stop
+    conversion_worker_stop = True
+    
+    # Wait for current conversions to finish if any are running
+    try:
+        if not conversion_queue.empty():
+            logger.info(f"Waiting for {conversion_queue.qsize()} pending conversions to complete...")
+            conversion_queue.join()
+            logger.info("All pending conversions completed")
+        else:
+            logger.info("No pending conversions")
+    except Exception as e:
+        logger.error(f"Error waiting for conversions to complete: {e}")
+    
+    logger.info("Conversion worker cleanup completed")
+
 def main():
     global download_stats
     logger = logging.getLogger(__name__)
@@ -1455,7 +1577,7 @@ def main():
     logger.info(f"Max concurrent conversions: {MAX_CONCURRENT_CONVERSIONS}")
     
     # Start background conversion worker
-    conversion_worker = threading.Thread(target=process_conversion_queue, daemon=True, name="ConversionQueueWorker")
+    conversion_worker = threading.Thread(target=process_conversion_queue, daemon=False, name="ConversionQueueWorker")
     conversion_worker.start()
     logger.debug("Conversion queue worker thread started")
     
@@ -1596,8 +1718,15 @@ def main():
     
     # Wait for all conversions to complete
     logger.info("Waiting for all conversions to complete...")
-    conversion_executor.shutdown(wait=True)
-    logger.info("All conversions completed!")
+    try:
+        # Wait for all queued conversions to finish
+        conversion_queue.join()
+        logger.info("All conversions completed!")
+    except KeyboardInterrupt:
+        logger.info("Interrupted while waiting for conversions")
+    finally:
+        # Clean up conversion worker
+        cleanup_conversion_worker()
     
     # Final Home Assistant update
     update_homeassistant_stats(
@@ -1791,15 +1920,19 @@ if __name__ == "__main__":
             schedule_downloads()
         except KeyboardInterrupt:
             logger.info("Service interrupted by user")
+            cleanup_conversion_worker()
         except Exception as e:
             logger.error(f"Service error: {e}", exc_info=True)
+            cleanup_conversion_worker()
     else:
         logger.info("Running YouTube Predownloader once")
         try:
             main()
         except KeyboardInterrupt:
             logger.info("Process interrupted by user")
+            cleanup_conversion_worker()
         except Exception as e:
             logger.error(f"Process error: {e}", exc_info=True)
+            cleanup_conversion_worker()
         finally:
             logger.info("Process finished")