Download plugins/trim_split.py from Thaimozhi/RenameBot: direct link, hf CLI and curl.
- Browser
- Download file 10.5 kB
-
https://huggingface.co/spaces/Thaimozhi/RenameBot/resolve/main/plugins/trim_split.py
- Command line
-
hf download hf://spaces/Thaimozhi/RenameBot/plugins/trim_split.py
-
curl -L -o trim_split.py https://huggingface.co/spaces/Thaimozhi/RenameBot/resolve/main/plugins/trim_split.py
10.5 kB
| import os | |
| import re | |
| import shutil | |
| import asyncio | |
| import logging | |
| import json | |
| from pyrogram import Client, filters | |
| from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message | |
| from pyrogram.errors import MessageNotModified | |
| logger = logging.getLogger(__name__) | |
| # βββ /trim entry point ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def trim_command(client, message: Message): | |
| """Must be a reply to a video/audio/document message.""" | |
| if not message.reply_to_message: | |
| return await message.reply_text( | |
| "β οΈ **Reply to a video/audio file** with /trim to start.") | |
| reply = message.reply_to_message | |
| if not (reply.video or reply.audio or reply.document): | |
| return await message.reply_text("β οΈ Replied message must contain a video or audio file.") | |
| keyboard = InlineKeyboardMarkup([[ | |
| InlineKeyboardButton("βοΈ Split by Parts", callback_data=f"trim_parts_{reply.id}"), | |
| InlineKeyboardButton("β± Trim by Time", callback_data=f"trim_time_{reply.id}"), | |
| ]]) | |
| await message.reply_text( | |
| "π¬ **Trim / Split**\n\nChoose an operation:", | |
| reply_markup=keyboard, quote=True) | |
| # βββ callback: split by parts ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def cb_trim_parts(client, query): | |
| m = re.match(r"^trim_parts_(\d+)$", query.data) | |
| if not m: | |
| return | |
| orig_msg_id = int(m.group(1)) | |
| user_id = query.from_user.id | |
| await query.message.edit_text("π’ **How many equal parts** do you want to split into?\n\n_Send a number (2β20):_") | |
| try: | |
| from pyromod.exceptions import ListenerTimeout | |
| resp = await client.listen( | |
| chat_id=query.message.chat.id, user_id=user_id, filters=filters.text, timeout=120) | |
| n_text = resp.text.strip() | |
| if not n_text.isdigit() or not (2 <= int(n_text) <= 20): | |
| await resp.delete() | |
| return await query.message.edit_text("β Please send a number between 2 and 20.") | |
| n_parts = int(n_text) | |
| await resp.delete() | |
| except ListenerTimeout: | |
| try: | |
| return await query.message.edit_text("β³ Timed out. Send /trim again.") | |
| except MessageNotModified: | |
| return | |
| orig = await client.get_messages(query.message.chat.id, orig_msg_id) | |
| await _do_split_parts(client, query.message, orig, n_parts) | |
| # βββ callback: trim by time ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def cb_trim_time(client, query): | |
| m = re.match(r"^trim_time_(\d+)$", query.data) | |
| if not m: | |
| return | |
| orig_msg_id = int(m.group(1)) | |
| user_id = query.from_user.id | |
| await query.message.edit_text( | |
| "β± **Enter Start and End timestamps**\n\nFormat: `HH:MM:SS - HH:MM:SS`\nExample: `00:01:30 - 00:05:00`") | |
| try: | |
| from pyromod.exceptions import ListenerTimeout | |
| resp = await client.listen( | |
| chat_id=query.message.chat.id, user_id=user_id, filters=filters.text, timeout=120) | |
| ts_text = resp.text.strip() | |
| await resp.delete() | |
| except ListenerTimeout: | |
| try: | |
| return await query.message.edit_text("β³ Timed out. Send /trim again.") | |
| except MessageNotModified: | |
| return | |
| # Support both "HH:MM:SS - HH:MM:SS" and "HH:MM:SS to HH:MM:SS" | |
| ts_text = ts_text.replace(" to ", " - ") | |
| sep_match = re.split(r'\s*-\s*', ts_text, maxsplit=1) | |
| if len(sep_match) != 2: | |
| return await query.message.edit_text( | |
| "β Invalid format. Use: `HH:MM:SS - HH:MM:SS`") | |
| start_ts, end_ts = sep_match[0].strip(), sep_match[1].strip() | |
| orig = await client.get_messages(query.message.chat.id, orig_msg_id) | |
| await _do_trim_time(client, query.message, orig, start_ts, end_ts) | |
| # βββ helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def _get_duration(file_path: str) -> float: | |
| """Return duration in seconds via ffprobe.""" | |
| ffprobe = shutil.which('ffprobe') | |
| if not ffprobe: | |
| return 0.0 | |
| cmd = [ffprobe, '-v', 'quiet', '-print_format', 'json', | |
| '-show_format', file_path] | |
| proc = await asyncio.create_subprocess_exec( | |
| *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) | |
| stdout, _ = await proc.communicate() | |
| try: | |
| import json | |
| data = json.loads(stdout.decode()) | |
| return float(data.get('format', {}).get('duration', 0)) | |
| except Exception: | |
| return 0.0 | |
| async def _download_media_message(client, message, status_msg): | |
| """Download media from a message and return the local path.""" | |
| if message.video: | |
| ext = ".mp4" | |
| elif message.audio: | |
| ext = f".{message.audio.mime_type.split('/')[-1]}" if message.audio.mime_type else ".mp3" | |
| elif message.document: | |
| name = message.document.file_name or "file" | |
| ext = os.path.splitext(name)[1] or ".mkv" | |
| else: | |
| ext = ".mp4" | |
| path = f"downloads/trim_{message.id}{ext}" | |
| os.makedirs("downloads", exist_ok=True) | |
| try: | |
| await status_msg.edit("**Downloading for processing...**") | |
| except MessageNotModified: | |
| pass | |
| file_path = await client.download_media(message, file_name=path) | |
| return file_path | |
| async def _do_split_parts(client, status_msg, orig_msg, n_parts: int): | |
| ffmpeg = shutil.which('ffmpeg') | |
| if not ffmpeg: | |
| try: | |
| return await status_msg.edit("β FFmpeg not found on this server.") | |
| except MessageNotModified: | |
| return | |
| file_path = await _download_media_message(client, orig_msg, status_msg) | |
| if not file_path: | |
| try: | |
| return await status_msg.edit("β Could not download the file.") | |
| except MessageNotModified: | |
| return | |
| try: | |
| duration = await _get_duration(file_path) | |
| if duration <= 0: | |
| try: | |
| return await status_msg.edit("β Could not determine file duration.") | |
| except MessageNotModified: | |
| return | |
| part_duration = duration / n_parts | |
| base, ext = os.path.splitext(file_path) | |
| output_paths = [] | |
| try: | |
| await status_msg.edit(f"βοΈ Splitting into **{n_parts} parts**...") | |
| except MessageNotModified: | |
| pass | |
| for i in range(n_parts): | |
| start = i * part_duration | |
| out_path = f"{base}_part{i+1}{ext}" | |
| cmd = [ | |
| ffmpeg, '-y', | |
| '-ss', str(start), | |
| '-i', file_path, | |
| '-t', str(part_duration), | |
| '-c', 'copy', | |
| '-loglevel', 'error', | |
| out_path | |
| ] | |
| proc = await asyncio.create_subprocess_exec( | |
| *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) | |
| _, stderr = await proc.communicate() | |
| if proc.returncode != 0: | |
| logger.error(f"ffmpeg split error part {i+1}: {stderr.decode()}") | |
| await status_msg.edit(f"β FFmpeg error on part {i+1}: {stderr.decode()[:200]}") | |
| return | |
| output_paths.append(out_path) | |
| await status_msg.edit(f"π€ Uploading **{n_parts} parts**...") | |
| for i, out_path in enumerate(output_paths): | |
| part_name = f"Part {i+1} of {n_parts}{ext}" | |
| await client.send_document( | |
| chat_id=status_msg.chat.id, | |
| document=out_path, | |
| file_name=part_name, | |
| caption=f"βοΈ Part {i+1}/{n_parts}" | |
| ) | |
| os.remove(out_path) | |
| await status_msg.edit("β All parts uploaded!") | |
| finally: | |
| if os.path.exists(file_path): | |
| os.remove(file_path) | |
| async def _do_trim_time(client, status_msg, orig_msg, start_ts: str, end_ts: str): | |
| ffmpeg = shutil.which('ffmpeg') | |
| if not ffmpeg: | |
| try: | |
| return await status_msg.edit("β FFmpeg not found on this server.") | |
| except MessageNotModified: | |
| return | |
| file_path = await _download_media_message(client, orig_msg, status_msg) | |
| if not file_path: | |
| try: | |
| return await status_msg.edit("β Could not download the file.") | |
| except MessageNotModified: | |
| return | |
| try: | |
| base, ext = os.path.splitext(file_path) | |
| out_path = f"{base}_trimmed{ext}" | |
| try: | |
| await status_msg.edit(f"βοΈ Trimming `{start_ts}` β `{end_ts}`...") | |
| except MessageNotModified: | |
| pass | |
| cmd = [ | |
| ffmpeg, '-y', | |
| '-ss', start_ts, | |
| '-to', end_ts, | |
| '-i', file_path, | |
| '-c', 'copy', | |
| '-loglevel', 'error', | |
| out_path | |
| ] | |
| proc = await asyncio.create_subprocess_exec( | |
| *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) | |
| _, stderr = await proc.communicate() | |
| if proc.returncode != 0: | |
| return await status_msg.edit(f"β FFmpeg error: {stderr.decode()[:300]}") | |
| await status_msg.edit("π€ Uploading trimmed file...") | |
| trimmed_name = f"Trimmed_{start_ts.replace(':','-')}_{end_ts.replace(':','-')}{ext}" | |
| if orig_msg.video: | |
| await client.send_video( | |
| chat_id=status_msg.chat.id, | |
| video=out_path, | |
| file_name=trimmed_name, | |
| caption=f"βοΈ Trimmed: `{start_ts}` β `{end_ts}`" | |
| ) | |
| else: | |
| await client.send_document( | |
| chat_id=status_msg.chat.id, | |
| document=out_path, | |
| file_name=trimmed_name, | |
| caption=f"βοΈ Trimmed: `{start_ts}` β `{end_ts}`" | |
| ) | |
| if os.path.exists(out_path): | |
| os.remove(out_path) | |
| await status_msg.edit("β Trimmed file uploaded!") | |
| finally: | |
| if os.path.exists(file_path): | |
| os.remove(file_path) | |