Download utils/ffmpeg.py from Thaimozhi/RenameBot: direct link, hf CLI and curl.
- Browser
- Download file 3.84 kB
-
https://huggingface.co/spaces/Thaimozhi/RenameBot/resolve/main/utils/ffmpeg.py
- Command line
-
hf download hf://spaces/Thaimozhi/RenameBot/utils/ffmpeg.py
-
curl -L -o ffmpeg.py https://huggingface.co/spaces/Thaimozhi/RenameBot/resolve/main/utils/ffmpeg.py
3.84 kB
| import asyncio | |
| import os | |
| import re | |
| import json | |
| async def run_cmd(cmd, user_id=None): | |
| process = await asyncio.create_subprocess_shell( | |
| cmd, | |
| stdout=asyncio.subprocess.PIPE, | |
| stderr=asyncio.subprocess.PIPE | |
| ) | |
| if user_id is not None: | |
| from utils.job_queue import USER_CANCELLATIONS | |
| while True: | |
| try: | |
| stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=2.0) | |
| return stdout.decode('utf-8', errors='ignore'), stderr.decode('utf-8', errors='ignore'), process.returncode | |
| except asyncio.TimeoutError: | |
| if user_id in USER_CANCELLATIONS: | |
| try: | |
| process.kill() | |
| except Exception: | |
| pass | |
| raise RuntimeError("TaskCancelledByUser") | |
| else: | |
| stdout, stderr = await process.communicate() | |
| return stdout.decode('utf-8', errors='ignore'), stderr.decode('utf-8', errors='ignore'), process.returncode | |
| async def get_video_resolution(file_path): | |
| cmd = f'ffprobe -v error -select_streams v -show_entries stream=width,height:stream_disposition=attached_pic -of json "{file_path}"' | |
| stdout, _, rc = await run_cmd(cmd) | |
| if rc == 0: | |
| try: | |
| data = json.loads(stdout) | |
| streams = data.get('streams', []) | |
| non_attached = [s for s in streams if s.get('disposition', {}).get('attached_pic', 0) != 1] | |
| candidate_streams = non_attached if non_attached else streams | |
| if not candidate_streams: | |
| return "Unknown" | |
| best_stream = max(candidate_streams, key=lambda s: int(s.get('height') or 0)) | |
| height = int(best_stream.get('height') or 0) | |
| if not height: | |
| return "Unknown" | |
| # Simple logic to determine common resolutions | |
| if height <= 480: | |
| return "480p" | |
| elif height <= 720: | |
| return "720p" | |
| elif height <= 1080: | |
| return "1080p" | |
| else: | |
| return f"{height}p" | |
| except Exception: | |
| return "Unknown" | |
| return "Unknown" | |
| async def strip_and_mux_audio(video_path, audio_path, output_path, user_id=None): | |
| # -map 0:v (video) | |
| # -map 1:a (audio) | |
| # -map 0:s? (subtitles optional) | |
| # -map 0:t? (fonts/attachments optional) | |
| cmd = f'ffmpeg -y -i "{video_path}" -i "{audio_path}" -map 0:v -map 1:a -map 0:s? -map 0:t? -c copy "{output_path}"' | |
| stdout, stderr, rc = await run_cmd(cmd, user_id) | |
| if rc != 0: | |
| print(f"ffmpeg failed with exit status {rc}. Retrying with stream-specific map...") | |
| cmd_fallback = f'ffmpeg -y -i "{video_path}" -i "{audio_path}" -map 0:v:0 -map 1:a:0 -map 0:s? -map 0:t? -c copy "{output_path}"' | |
| stdout, stderr, rc = await run_cmd(cmd_fallback, user_id) | |
| if rc != 0: | |
| print(f"ffmpeg fallback failed. STDOUT: {stdout} | STDERR: {stderr}") | |
| return rc == 0 | |
| async def extract_thumbnail(video_path, output_path): | |
| # Extract frame at 2 seconds | |
| cmd = f'ffmpeg -y -ss 00:00:02 -i "{video_path}" -vframes 1 -q:v 2 "{output_path}"' | |
| _, _, rc = await run_cmd(cmd) | |
| return rc == 0 | |
| async def get_media_info(file_path): | |
| cmd = f'ffprobe -v quiet -print_format json -show_format -show_streams "{file_path}"' | |
| stdout, _, rc = await run_cmd(cmd) | |
| if rc == 0: | |
| try: | |
| return json.loads(stdout) | |
| except Exception: | |
| pass | |
| return {} | |
| async def extract_stream(video_path, stream_index, output_path): | |
| # e.g. stream_index = "0:a:1" | |
| cmd = f'ffmpeg -y -i "{video_path}" -map 0:{stream_index} -c copy "{output_path}"' | |
| _, _, rc = await run_cmd(cmd) | |
| return rc == 0 | |