2025-07-11 18:02:44 +08:00
|
|
|
import os
|
|
|
|
|
import subprocess
|
|
|
|
|
import time
|
|
|
|
|
import requests
|
|
|
|
|
from requests.exceptions import RequestException, Timeout
|
|
|
|
|
from urllib.parse import urlparse, unquote
|
|
|
|
|
|
|
|
|
|
def download_video(video_url, draft_name, material_name):
|
|
|
|
|
"""
|
2025-07-13 16:00:53 +08:00
|
|
|
Download video to specified directory
|
|
|
|
|
:param video_url: Video URL
|
|
|
|
|
:param draft_name: Draft name
|
|
|
|
|
:param material_name: Material name
|
|
|
|
|
:return: Local video path
|
2025-07-11 18:02:44 +08:00
|
|
|
"""
|
2025-07-13 16:00:53 +08:00
|
|
|
# Ensure directory exists
|
2025-07-11 18:02:44 +08:00
|
|
|
video_dir = f"{draft_name}/assets/video"
|
|
|
|
|
os.makedirs(video_dir, exist_ok=True)
|
|
|
|
|
|
2025-07-13 16:00:53 +08:00
|
|
|
# Generate local filename
|
2025-07-11 18:02:44 +08:00
|
|
|
local_path = f"{video_dir}/{material_name}"
|
|
|
|
|
|
2025-07-13 16:00:53 +08:00
|
|
|
# Check if file already exists
|
2025-07-11 18:02:44 +08:00
|
|
|
if os.path.exists(local_path):
|
2025-07-13 16:00:53 +08:00
|
|
|
print(f"Video file already exists: {local_path}")
|
2025-07-11 18:02:44 +08:00
|
|
|
return local_path
|
|
|
|
|
|
|
|
|
|
try:
|
2025-07-13 16:00:53 +08:00
|
|
|
# Use ffmpeg to download video
|
2025-07-11 18:02:44 +08:00
|
|
|
command = [
|
|
|
|
|
'ffmpeg',
|
|
|
|
|
'-i', video_url,
|
2025-07-13 16:00:53 +08:00
|
|
|
'-c', 'copy', # Direct copy, no re-encoding
|
2025-07-11 18:02:44 +08:00
|
|
|
local_path
|
|
|
|
|
]
|
|
|
|
|
subprocess.run(command, check=True, capture_output=True)
|
|
|
|
|
return local_path
|
|
|
|
|
except subprocess.CalledProcessError as e:
|
2025-07-13 16:00:53 +08:00
|
|
|
raise Exception(f"Failed to download video: {e.stderr.decode('utf-8')}")
|
2025-07-11 18:02:44 +08:00
|
|
|
|
|
|
|
|
def download_image(image_url, draft_name, material_name):
|
|
|
|
|
"""
|
2025-07-13 16:00:53 +08:00
|
|
|
Download image to specified directory, and convert to PNG format
|
|
|
|
|
:param image_url: Image URL
|
|
|
|
|
:param draft_name: Draft name
|
|
|
|
|
:param material_name: Material name
|
|
|
|
|
:return: Local image path
|
2025-07-11 18:02:44 +08:00
|
|
|
"""
|
2025-07-13 16:00:53 +08:00
|
|
|
# Ensure directory exists
|
2025-07-11 18:02:44 +08:00
|
|
|
image_dir = f"{draft_name}/assets/image"
|
|
|
|
|
os.makedirs(image_dir, exist_ok=True)
|
|
|
|
|
|
2025-07-13 16:00:53 +08:00
|
|
|
# Uniformly use png format
|
2025-07-11 18:02:44 +08:00
|
|
|
local_path = f"{image_dir}/{material_name}"
|
|
|
|
|
|
2025-07-13 16:00:53 +08:00
|
|
|
# Check if file already exists
|
2025-07-11 18:02:44 +08:00
|
|
|
if os.path.exists(local_path):
|
2025-07-13 16:00:53 +08:00
|
|
|
print(f"Image file already exists: {local_path}")
|
2025-07-11 18:02:44 +08:00
|
|
|
return local_path
|
|
|
|
|
|
|
|
|
|
try:
|
2025-07-13 16:00:53 +08:00
|
|
|
# Use ffmpeg to download and convert image to PNG format
|
2025-07-11 18:02:44 +08:00
|
|
|
command = [
|
|
|
|
|
'ffmpeg',
|
|
|
|
|
'-i', image_url,
|
2025-07-13 16:00:53 +08:00
|
|
|
'-vf', 'format=rgba', # Convert to RGBA format to support transparency
|
|
|
|
|
'-frames:v', '1', # Ensure only one frame is processed
|
|
|
|
|
'-y', # Overwrite existing files
|
2025-07-11 18:02:44 +08:00
|
|
|
local_path
|
|
|
|
|
]
|
|
|
|
|
subprocess.run(command, check=True, capture_output=True)
|
|
|
|
|
return local_path
|
|
|
|
|
except subprocess.CalledProcessError as e:
|
2025-07-13 16:00:53 +08:00
|
|
|
raise Exception(f"Failed to download image: {e.stderr.decode('utf-8')}")
|
2025-07-11 18:02:44 +08:00
|
|
|
|
|
|
|
|
def download_audio(audio_url, draft_name, material_name):
|
|
|
|
|
"""
|
2025-07-13 16:00:53 +08:00
|
|
|
Download audio and transcode to MP3 format to specified directory
|
|
|
|
|
:param audio_url: Audio URL
|
|
|
|
|
:param draft_name: Draft name
|
|
|
|
|
:param material_name: Material name
|
|
|
|
|
:return: Local audio path
|
2025-07-11 18:02:44 +08:00
|
|
|
"""
|
2025-07-13 16:00:53 +08:00
|
|
|
# Ensure directory exists
|
2025-07-11 18:02:44 +08:00
|
|
|
audio_dir = f"{draft_name}/assets/audio"
|
|
|
|
|
os.makedirs(audio_dir, exist_ok=True)
|
|
|
|
|
|
2025-07-13 16:00:53 +08:00
|
|
|
# Generate local filename (keep .mp3 extension)
|
2025-07-11 18:02:44 +08:00
|
|
|
local_path = f"{audio_dir}/{material_name}"
|
|
|
|
|
|
2025-07-13 16:00:53 +08:00
|
|
|
# Check if file already exists
|
2025-07-11 18:02:44 +08:00
|
|
|
if os.path.exists(local_path):
|
2025-07-13 16:00:53 +08:00
|
|
|
print(f"Audio file already exists: {local_path}")
|
2025-07-11 18:02:44 +08:00
|
|
|
return local_path
|
|
|
|
|
|
|
|
|
|
try:
|
2025-07-13 16:00:53 +08:00
|
|
|
# Use ffmpeg to download and transcode to MP3 (key modification: specify MP3 encoder)
|
2025-07-11 18:02:44 +08:00
|
|
|
command = [
|
|
|
|
|
'ffmpeg',
|
2025-07-13 16:00:53 +08:00
|
|
|
'-i', audio_url, # Input URL
|
|
|
|
|
'-c:a', 'libmp3lame', # Force encode audio stream to MP3
|
|
|
|
|
'-q:a', '2', # Set audio quality (0-9, 0 is best, 2 balances quality and file size)
|
|
|
|
|
'-y', # Overwrite existing files (optional)
|
|
|
|
|
local_path # Output path
|
2025-07-11 18:02:44 +08:00
|
|
|
]
|
|
|
|
|
subprocess.run(command, check=True, capture_output=True, text=True)
|
|
|
|
|
return local_path
|
|
|
|
|
except subprocess.CalledProcessError as e:
|
2025-07-13 16:00:53 +08:00
|
|
|
raise Exception(f"Failed to download audio:\n{e.stderr}")
|
2025-07-11 18:02:44 +08:00
|
|
|
|
|
|
|
|
def download_file(url:str, local_filename, max_retries=3, timeout=180):
|
2025-07-13 16:00:53 +08:00
|
|
|
# Extract directory part
|
2025-07-11 18:02:44 +08:00
|
|
|
directory = os.path.dirname(local_filename)
|
|
|
|
|
|
|
|
|
|
retries = 0
|
|
|
|
|
while retries < max_retries:
|
|
|
|
|
try:
|
|
|
|
|
if retries > 0:
|
2025-07-13 16:00:53 +08:00
|
|
|
wait_time = 2 ** retries # Exponential backoff strategy
|
2025-07-11 18:02:44 +08:00
|
|
|
print(f"Retrying in {wait_time} seconds... (Attempt {retries+1}/{max_retries})")
|
|
|
|
|
time.sleep(wait_time)
|
|
|
|
|
|
|
|
|
|
print(f"Downloading file: {local_filename}")
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
|
2025-07-13 16:00:53 +08:00
|
|
|
# Create directory (if it doesn't exist)
|
2025-07-11 18:02:44 +08:00
|
|
|
if directory and not os.path.exists(directory):
|
|
|
|
|
os.makedirs(directory, exist_ok=True)
|
|
|
|
|
print(f"Created directory: {directory}")
|
|
|
|
|
|
|
|
|
|
with requests.get(url, stream=True, timeout=timeout) as response:
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
|
|
|
|
|
total_size = int(response.headers.get('content-length', 0))
|
|
|
|
|
block_size = 1024
|
|
|
|
|
|
|
|
|
|
with open(local_filename, 'wb') as file:
|
|
|
|
|
bytes_written = 0
|
|
|
|
|
for chunk in response.iter_content(block_size):
|
|
|
|
|
if chunk:
|
|
|
|
|
file.write(chunk)
|
|
|
|
|
bytes_written += len(chunk)
|
|
|
|
|
|
|
|
|
|
if total_size > 0:
|
|
|
|
|
progress = bytes_written / total_size * 100
|
2025-07-13 16:00:53 +08:00
|
|
|
# For frequently updated progress, consider using logger.debug or more granular control to avoid large log files
|
|
|
|
|
# Or only output progress to console, not write to file
|
2025-07-11 18:02:44 +08:00
|
|
|
print(f"\r[PROGRESS] {progress:.2f}% ({bytes_written/1024:.2f}KB/{total_size/1024:.2f}KB)", end='')
|
2025-07-13 16:00:53 +08:00
|
|
|
pass # Avoid printing too much progress information in log files
|
2025-07-11 18:02:44 +08:00
|
|
|
|
|
|
|
|
if total_size > 0:
|
2025-07-13 16:00:53 +08:00
|
|
|
# print() # Original newline
|
2025-07-11 18:02:44 +08:00
|
|
|
pass
|
|
|
|
|
print(f"Download completed in {time.time()-start_time:.2f} seconds")
|
|
|
|
|
print(f"File saved as: {os.path.abspath(local_filename)}")
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
except Timeout:
|
|
|
|
|
print(f"Download timed out after {timeout} seconds")
|
|
|
|
|
except RequestException as e:
|
|
|
|
|
print(f"Request failed: {e}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Unexpected error during download: {e}")
|
|
|
|
|
|
|
|
|
|
retries += 1
|
|
|
|
|
|
|
|
|
|
print(f"Download failed after {max_retries} attempts for URL: {url}")
|
|
|
|
|
return False
|
|
|
|
|
|