File size: 6,438 Bytes
11fa042 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | import os
import asyncio
import time
import re
from pyrogram import Client, filters
from ..utils.helper import check_chat
from ..svcs.user_svc import add_user_if_new
from ..core.cfg import cfg
from ..core.log import log
from ..utils.display_progress import progress_for_pyrogram, humanbytes
from ..utils.encoding import get_duration
def parse_time(time_str):
time_str = time_str.strip()
parts = time_str.split(':')
if len(parts) == 3:
h, m, s = parts
return int(h) * 3600 + int(m) * 60 + float(s)
elif len(parts) == 2:
m, s = parts
return int(m) * 60 + float(s)
else:
return float(time_str)
def format_time(seconds):
h = int(seconds // 3600)
m = int((seconds % 3600) // 60)
s = int(seconds % 60)
return f"{h:02d}:{m:02d}:{s:02d}"
@Client.on_message(filters.command(['trim', 'cut']))
async def trim_handler(app, message):
if not await check_chat(message, chat='Both'):
return
if not message.reply_to_message:
await message.reply(
"▸ <b>Trim Video</b>\n"
"Status: ✗ Failed\n\n"
"<blockquote>Reply to a video with this command.\n\n"
"Usage: /trim [start] [end]\n"
"Examples:\n"
"• /trim 00:01:00 00:05:00\n"
"• /trim 1:30 5:00\n"
"• /trim 90 300 (seconds)</blockquote>"
)
return
reply = message.reply_to_message
if not (reply.video or (reply.document and reply.document.mime_type and reply.document.mime_type.startswith('video/'))):
await message.reply(
"▸ <b>Trim Video</b>\n"
"Status: ✗ Failed\n\n"
"<blockquote>The replied message is not a video.</blockquote>"
)
return
if len(message.command) < 3:
await message.reply(
"▸ <b>Trim Video</b>\n"
"Status: ✗ Invalid Arguments\n\n"
"<blockquote>Please provide start and end times.\n"
"Example: /trim 00:01:00 00:05:00</blockquote>"
)
return
try:
start_time = parse_time(message.command[1])
end_time = parse_time(message.command[2])
except Exception as e:
await message.reply(
"▸ <b>Trim Video</b>\n"
"Status: ✗ Invalid Time Format\n\n"
"<blockquote>Use HH:MM:SS, MM:SS, or seconds.\n"
"Example: /trim 1:30 5:00</blockquote>"
)
return
if start_time >= end_time:
await message.reply(
"▸ <b>Trim Video</b>\n"
"Status: ✗ Invalid Range\n\n"
"<blockquote>Start time must be less than end time.</blockquote>"
)
return
duration = end_time - start_time
await add_user_if_new(app, message)
msg = await message.reply(
f"▸ <b>Trim Video</b>\n"
f"Status: ● Downloading...\n"
f"Range: {format_time(start_time)} → {format_time(end_time)}\n"
f"Duration: {format_time(duration)}"
)
filepath = None
output_path = None
try:
dl_start = time.time()
filepath = await reply.download(
file_name=cfg.DOWNLOAD_DIR,
progress=progress_for_pyrogram,
progress_args=("▸ <b>Trim</b>\nStatus: ● Downloading...", msg, dl_start)
)
if not filepath:
await msg.edit("▸ <b>Trim</b>\nStatus: ✗ Download Failed")
return
video_duration = get_duration(filepath)
if end_time > video_duration:
await msg.edit(
f"▸ <b>Trim</b>\nStatus: ✗ Invalid Range\n\n"
f"<blockquote>End time ({format_time(end_time)}) exceeds video duration ({format_time(video_duration)}).</blockquote>"
)
return
await msg.edit(
f"▸ <b>Trim Video</b>\n"
f"Status: ● Cutting video...\n"
f"Range: {format_time(start_time)} → {format_time(end_time)}"
)
basename = os.path.basename(filepath)
name, ext = os.path.splitext(basename)
output_path = os.path.join(cfg.ENCODE_DIR, f"trim_{name}{ext}")
cmd = [
'ffmpeg', '-y', '-ss', str(start_time),
'-i', filepath, '-t', str(duration),
'-c', 'copy', '-avoid_negative_ts', '1',
output_path
]
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
await proc.communicate()
if proc.returncode != 0 or not os.path.exists(output_path):
await msg.edit("▸ <b>Trim</b>\nStatus: ● Re-encoding (stream copy failed)...")
cmd = [
'ffmpeg', '-y', '-ss', str(start_time),
'-i', filepath, '-t', str(duration),
'-c:v', 'libx264', '-preset', 'veryfast', '-crf', '23',
'-c:a', 'aac', '-b:a', '128k',
output_path
]
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
await proc.communicate()
if not os.path.exists(output_path) or os.path.getsize(output_path) == 0:
await msg.edit("▸ <b>Trim</b>\nStatus: ✗ Failed to trim video")
return
await msg.edit("▸ <b>Trim</b>\nStatus: ● Uploading...")
output_size = os.path.getsize(output_path)
await reply.reply_video(
output_path,
caption=(
f"▸ <b>Trimmed Video</b>\n"
f"Range: {format_time(start_time)} → {format_time(end_time)}\n"
f"Duration: {format_time(duration)} | Size: {humanbytes(output_size)}"
),
supports_streaming=True
)
await msg.delete()
except Exception as e:
log.err("trim_error", error=str(e))
await msg.edit(f"▸ <b>Trim</b>\nStatus: ✗ Error\n{str(e)[:100]}")
finally:
if filepath and os.path.exists(filepath):
os.remove(filepath)
if output_path and os.path.exists(output_path):
os.remove(output_path)
|