2025-07-11 18:02:44 +08:00
|
|
|
import subprocess
|
|
|
|
|
import json
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
def get_video_duration(video_url):
|
|
|
|
|
"""
|
2025-07-13 15:09:36 +08:00
|
|
|
Get video duration with timeout retry support.
|
|
|
|
|
:param video_url: Video URL
|
|
|
|
|
:return: Video duration (seconds)
|
2025-07-11 18:02:44 +08:00
|
|
|
"""
|
|
|
|
|
|
2025-07-13 15:09:36 +08:00
|
|
|
# Define retry count and wait time for each retry
|
2025-07-11 18:02:44 +08:00
|
|
|
max_retries = 3
|
2025-07-13 15:09:36 +08:00
|
|
|
retry_delay_seconds = 1 # 1 second interval between retries
|
|
|
|
|
timeout_seconds = 10 # Set timeout for each attempt
|
2025-07-11 18:02:44 +08:00
|
|
|
|
|
|
|
|
for attempt in range(max_retries):
|
2025-07-13 15:09:36 +08:00
|
|
|
print(f"Attempting to get video duration (Attempt {attempt + 1}/{max_retries}) ...")
|
|
|
|
|
result = {"success": False, "output": 0, "error": None} # Reset result before each retry
|
2025-07-11 18:02:44 +08:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
command = [
|
|
|
|
|
'ffprobe',
|
|
|
|
|
'-v', 'error',
|
|
|
|
|
'-show_entries', 'stream=duration',
|
|
|
|
|
'-show_entries', 'format=duration',
|
|
|
|
|
'-print_format', 'json',
|
|
|
|
|
video_url
|
|
|
|
|
]
|
|
|
|
|
|
2025-07-13 15:09:36 +08:00
|
|
|
# Use subprocess.run for more flexible handling of timeout and output
|
2025-07-11 18:02:44 +08:00
|
|
|
process = subprocess.run(command,
|
|
|
|
|
capture_output=True,
|
2025-07-13 15:09:36 +08:00
|
|
|
text=True, # Auto decode to text
|
|
|
|
|
timeout=timeout_seconds, # Use variable to set timeout
|
|
|
|
|
check=True) # Raise CalledProcessError if non-zero exit code
|
2025-07-11 18:02:44 +08:00
|
|
|
|
|
|
|
|
info = json.loads(process.stdout)
|
|
|
|
|
|
2025-07-13 15:09:36 +08:00
|
|
|
# Prioritize getting duration from streams because it's more accurate
|
2025-07-11 18:02:44 +08:00
|
|
|
media_streams = [s for s in info.get('streams', []) if 'duration' in s]
|
|
|
|
|
|
|
|
|
|
if media_streams:
|
|
|
|
|
duration = float(media_streams[0]['duration'])
|
|
|
|
|
result["output"] = duration
|
|
|
|
|
result["success"] = True
|
2025-07-13 15:09:36 +08:00
|
|
|
# Otherwise get duration from format information
|
2025-07-11 18:02:44 +08:00
|
|
|
elif 'format' in info and 'duration' in info['format']:
|
|
|
|
|
duration = float(info['format']['duration'])
|
|
|
|
|
result["output"] = duration
|
|
|
|
|
result["success"] = True
|
|
|
|
|
else:
|
2025-07-13 15:09:36 +08:00
|
|
|
result["error"] = "Audio/video duration information not found."
|
2025-07-11 18:02:44 +08:00
|
|
|
|
2025-07-13 15:09:36 +08:00
|
|
|
# If duration is successfully obtained, return result directly without retrying
|
2025-07-11 18:02:44 +08:00
|
|
|
if result["success"]:
|
2025-07-13 15:09:36 +08:00
|
|
|
print(f"Successfully obtained duration: {result['output']:.2f} seconds")
|
2025-07-11 18:02:44 +08:00
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
except subprocess.TimeoutExpired:
|
2025-07-13 15:09:36 +08:00
|
|
|
result["error"] = f"Getting video duration timed out (exceeded {timeout_seconds} seconds)."
|
|
|
|
|
print(f"Attempt {attempt + 1} timed out.")
|
2025-07-11 18:02:44 +08:00
|
|
|
except subprocess.CalledProcessError as e:
|
2025-07-13 15:09:36 +08:00
|
|
|
result["error"] = f"Error executing ffprobe command (exit code {e.returncode}): {e.stderr.strip()}"
|
|
|
|
|
print(f"Attempt {attempt + 1} failed. Error: {e.stderr.strip()}")
|
2025-07-11 18:02:44 +08:00
|
|
|
except json.JSONDecodeError as e:
|
2025-07-13 15:09:36 +08:00
|
|
|
result["error"] = f"Error parsing JSON data: {e}"
|
|
|
|
|
print(f"Attempt {attempt + 1} failed. JSON parsing error: {e}")
|
2025-07-11 18:02:44 +08:00
|
|
|
except FileNotFoundError:
|
2025-07-13 15:09:36 +08:00
|
|
|
result["error"] = "ffprobe command not found. Please ensure FFmpeg is installed and in system PATH."
|
|
|
|
|
print("Error: ffprobe command not found, please check installation.")
|
|
|
|
|
return result # No need to retry if ffprobe itself is not found
|
2025-07-11 18:02:44 +08:00
|
|
|
except Exception as e:
|
2025-07-13 15:09:36 +08:00
|
|
|
result["error"] = f"Unknown error occurred: {e}"
|
|
|
|
|
print(f"Attempt {attempt + 1} failed. Unknown error: {e}")
|
2025-07-11 18:02:44 +08:00
|
|
|
|
2025-07-13 15:09:36 +08:00
|
|
|
# Try using remote service to get duration after each local failure
|
2025-07-11 18:02:44 +08:00
|
|
|
if not result["success"]:
|
2025-07-13 15:09:36 +08:00
|
|
|
print(f"Local retrieval failed")
|
2025-07-11 18:02:44 +08:00
|
|
|
# try:
|
|
|
|
|
# remote_duration = get_duration(video_url)
|
|
|
|
|
# if remote_duration is not None:
|
|
|
|
|
# result["success"] = True
|
|
|
|
|
# result["output"] = remote_duration
|
|
|
|
|
# result["error"] = None
|
2025-07-13 15:09:36 +08:00
|
|
|
# print(f"Remote service successfully obtained duration: {remote_duration:.2f} seconds")
|
|
|
|
|
# return result # Remote service succeeded, return directly
|
2025-07-11 18:02:44 +08:00
|
|
|
# else:
|
2025-07-13 15:09:36 +08:00
|
|
|
# print(f"Remote service also unable to get duration (Attempt {attempt + 1})")
|
2025-07-11 18:02:44 +08:00
|
|
|
# except Exception as e:
|
2025-07-13 15:09:36 +08:00
|
|
|
# print(f"Remote service failed to get duration (Attempt {attempt + 1}): {e}")
|
2025-07-11 18:02:44 +08:00
|
|
|
|
2025-07-13 15:09:36 +08:00
|
|
|
# If current attempt failed and max retries not reached, wait and prepare for next retry
|
2025-07-11 18:02:44 +08:00
|
|
|
if not result["success"] and attempt < max_retries - 1:
|
2025-07-13 15:09:36 +08:00
|
|
|
print(f"Waiting {retry_delay_seconds} seconds before retrying...")
|
2025-07-11 18:02:44 +08:00
|
|
|
time.sleep(retry_delay_seconds)
|
|
|
|
|
elif not result["success"] and attempt == max_retries - 1:
|
2025-07-13 15:09:36 +08:00
|
|
|
print(f"Maximum retry count {max_retries} reached, both local and remote services unable to get duration.")
|
2025-07-11 18:02:44 +08:00
|
|
|
|
2025-07-13 15:09:36 +08:00
|
|
|
return result # Return the last failure result after all retries fail
|