Kirito-ai commited on
Commit
9f44377
·
verified ·
1 Parent(s): b8a0b5b

Delete helper_func/mux.py

Browse files
Files changed (1) hide show
  1. helper_func/mux.py +0 -539
helper_func/mux.py DELETED
@@ -1,539 +0,0 @@
1
- from config import Config
2
- import time
3
- import re
4
- import asyncio
5
- import os
6
- import logging
7
- from pyrogram import Client
8
- from datetime import datetime, timedelta
9
-
10
-
11
- progress_pattern = re.compile(
12
- r'(frame|fps|size|time|bitrate|speed)\s*\=\s*(\S+)'
13
- )
14
-
15
- def parse_progress(line):
16
- items = {
17
- key: value for key, value in progress_pattern.findall(line)
18
- }
19
- if not items:
20
- return None
21
- return items
22
-
23
- async def readlines(stream):
24
- pattern = re.compile(br'[\r\n]+')
25
-
26
- data = bytearray()
27
- while not stream.at_eof():
28
- lines = pattern.split(data)
29
- data[:] = lines.pop(-1)
30
-
31
- for line in lines:
32
- yield line
33
-
34
- data.extend(await stream.read(1024))
35
-
36
- # Add a dictionary to store encoding progress data for each user
37
- encoding_progress = {}
38
-
39
- # Add at the top with other global variables
40
- active_processes = {} # To track active downloads and encodings per user
41
-
42
- async def read_stderr(start, msg, process):
43
- # Get chat_id from the message to track progress per user
44
- chat_id = msg.chat.id
45
-
46
- # Initialize progress tracking for this user
47
- if chat_id not in encoding_progress:
48
- encoding_progress[chat_id] = {
49
- 'last_update_time': time.time(),
50
- 'total_duration': None,
51
- 'speed_samples': [],
52
- 'max_samples': 10
53
- }
54
-
55
- user_progress = encoding_progress[chat_id]
56
- duration_pattern = re.compile(r'Duration: (\d+):(\d+):(\d+\.\d+)')
57
-
58
- async for line in readlines(process.stderr):
59
- line = line.decode('utf-8')
60
-
61
- # Try to extract duration if we don't have it yet
62
- if user_progress['total_duration'] is None:
63
- duration_match = duration_pattern.search(line)
64
- if duration_match:
65
- h, m, s = duration_match.groups()
66
- user_progress['total_duration'] = float(h) * 3600 + float(m) * 60 + float(s)
67
-
68
- progress = parse_progress(line)
69
- if progress:
70
- now = time.time()
71
- elapsed = now - start
72
-
73
- # Build basic progress message
74
- text = '🎬 <b>Encoding in Progress</b>\n\n'
75
- text += '📊 Size: {}\n'.format(format_size(progress['size']))
76
- text += '⏱️ Time: {}\n'.format(progress['time'])
77
- text += '⚡ Speed: {}\n'.format(progress['speed'])
78
-
79
- # Add estimated time remaining if we can calculate it
80
- if 'time' in progress and user_progress['total_duration'] is not None:
81
- # Parse the current timestamp
82
- time_parts = progress['time'].split(':')
83
- if len(time_parts) == 3:
84
- h, m, s = time_parts
85
- current_time = float(h) * 3600 + float(m) * 60 + float(s)
86
-
87
- # Calculate progress percentage
88
- percent_done = min(99.9, (current_time / user_progress['total_duration']) * 100)
89
- text += '📈 Progress: {:.1f}%\n'.format(percent_done)
90
-
91
- # Extract speed for better estimation
92
- if 'speed' in progress:
93
- speed_match = re.search(r'(\d+\.?\d*)x', progress['speed'])
94
- if speed_match:
95
- speed = float(speed_match.group(1))
96
-
97
- # Add to our speed samples
98
- user_progress['speed_samples'].append((now, speed, current_time))
99
-
100
- # Keep only recent samples
101
- if len(user_progress['speed_samples']) > user_progress['max_samples']:
102
- user_progress['speed_samples'].pop(0)
103
-
104
- # Calculate weighted average speed (more recent samples have higher weight)
105
- if len(user_progress['speed_samples']) >= 3:
106
- total_weight = 0
107
- weighted_speed = 0
108
-
109
- for i, (sample_time, sample_speed, _) in enumerate(user_progress['speed_samples']):
110
- weight = (i + 1) ** 2
111
- weighted_speed += sample_speed * weight
112
- total_weight += weight
113
-
114
- avg_speed = weighted_speed / total_weight if total_weight > 0 else speed
115
-
116
- # Calculate remaining time
117
- remaining_time = (user_progress['total_duration'] - current_time) / avg_speed
118
- text += '⏳ ETA: {}\n'.format(format_time_duration(remaining_time))
119
- else:
120
- # Fallback method if speed isn't available
121
- if percent_done > 0:
122
- estimated_total = elapsed / (percent_done / 100)
123
- remaining = estimated_total - elapsed
124
-
125
- # Apply a safety factor
126
- remaining *= 2.5 # Multiply the estimate to be safe
127
-
128
- if remaining > 0:
129
- text += 'Estimated time remaining: {} (rough estimate)\n'.format(format_time_duration(remaining))
130
-
131
- # Update message at regular intervals
132
- if round(now - user_progress['last_update_time']) >= 5:
133
- try:
134
- await msg.edit(text)
135
- user_progress['last_update_time'] = now
136
- except:
137
- pass
138
-
139
- # Clean up when encoding is done
140
- if chat_id in encoding_progress:
141
- del encoding_progress[chat_id]
142
-
143
- def format_time_duration(seconds):
144
- """Format seconds into human readable time (e.g., 2h 30m 5s)"""
145
- hours = seconds // 3600
146
- minutes = (seconds % 3600) // 60
147
- seconds = seconds % 60
148
-
149
- parts = []
150
- if hours > 0:
151
- parts.append(f"{int(hours)}h")
152
- if minutes > 0:
153
- parts.append(f"{int(minutes)}m")
154
- if seconds > 0 or not parts: # include seconds if it's the only non-zero value
155
- parts.append(f"{int(seconds)}s")
156
-
157
- return " ".join(parts)
158
-
159
- def format_size(size_str):
160
- """Convert size string to human readable format"""
161
- try:
162
- # Remove 'kB' or any other suffix and convert to bytes
163
- size = float(size_str.replace('kB', '')) * 1024 # Convert kB to bytes
164
-
165
- for unit in ['B', 'KB', 'MB', 'GB']:
166
- if size < 1024.0:
167
- return "{:.2f} {}".format(size, unit)
168
- size /= 1024.0
169
- return "{:.2f} TB".format(size)
170
- except:
171
- return size_str # Return original string if conversion fails
172
-
173
- async def softmux_vid(vid_filename, sub_filename, msg):
174
- chat_id = msg.chat.id
175
-
176
- # Set active process
177
- active_processes[chat_id] = 'encoding'
178
- start = time.time()
179
- out_location = None # Define outside try block for cleanup
180
-
181
- try:
182
- vid = Config.DOWNLOAD_DIR+'/'+vid_filename
183
- sub = Config.DOWNLOAD_DIR+'/'+sub_filename
184
-
185
- out_file = '.'.join(vid_filename.split('.')[:-1])
186
- output = out_file+'1.mkv'
187
- out_location = Config.DOWNLOAD_DIR+'/'+output
188
- sub_ext = sub_filename.split('.')[-1].lower()
189
-
190
- # Set the appropriate subtitle codec based on file extension
191
- if sub_ext == 'vtt':
192
- subtitle_codec = 'webvtt'
193
- else:
194
- subtitle_codec = sub_ext
195
-
196
- command = [
197
- 'ffmpeg', '-hide_banner',
198
- '-fflags', '+genpts',
199
- '-i',vid,
200
- '-i',sub,
201
- '-map','1:0','-map','0',
202
- '-disposition:s:0','default',
203
- '-c:v','copy',
204
- '-c:a','copy',
205
- '-c:s',subtitle_codec,
206
- '-y',out_location
207
- ]
208
-
209
- process = await asyncio.create_subprocess_exec(
210
- *command,
211
- stdout=asyncio.subprocess.PIPE,
212
- stderr=asyncio.subprocess.PIPE,
213
- )
214
-
215
- await asyncio.wait([
216
- asyncio.create_task(read_stderr(start, msg, process)),
217
- asyncio.create_task(process.wait())
218
- ])
219
-
220
- if process.returncode == 0:
221
- elapsed_time = round(time.time() - start)
222
- await msg.edit(
223
- f'✅ <b>Encoding Completed Successfully!</b>\n\n'
224
- f'⏱️ Time Taken: {format_time_duration(elapsed_time)}'
225
- )
226
- await asyncio.sleep(1)
227
- return output
228
- else:
229
- await msg.edit(
230
- '❌ <b>Encoding Failed</b>\n\n'
231
- 'An error occurred during the encoding process.\n'
232
- 'Please try again or use different settings.'
233
- )
234
- # Clean up failed encoding file
235
- if out_location and os.path.exists(out_location):
236
- try:
237
- os.remove(out_location)
238
- logging.info(f"Removed failed encoding file: {output}")
239
- except Exception as e:
240
- logging.error(f"Error removing failed encoding file: {e}")
241
- return False
242
-
243
- except Exception as e:
244
- logging.error(f"Error during encoding: {e}")
245
- # Clean up failed encoding file
246
- if out_location and os.path.exists(out_location):
247
- try:
248
- os.remove(out_location)
249
- logging.info(f"Removed failed encoding file: {output}")
250
- except Exception as e:
251
- logging.error(f"Error removing failed encoding file: {e}")
252
- await msg.edit(
253
- '❌ <b>Encoding Error</b>\n\n'
254
- 'An unexpected error occurred.\n'
255
- 'Please try again or contact support.'
256
- )
257
- return False
258
- finally:
259
- # Remove active process status
260
- if chat_id in active_processes:
261
- del active_processes[chat_id]
262
-
263
- # Define resolution options
264
- RESOLUTIONS = {
265
- '8k': '7680x4320',
266
- '4k': '3840x2160',
267
- '1440p': '2560x1440',
268
- '1080p': '1920x1080',
269
- '720p': '1280x720',
270
- '480p': '854x480',
271
- '360p': '640x360',
272
- '240p': '426x240',
273
- '144p': '256x144',
274
- 'original': 'original'
275
- }
276
-
277
- # Define codec options
278
- CODECS = {
279
- 'h264': {
280
- 'ffmpeg_codec': 'libx264',
281
- 'description': 'H.264 (AVC) - Widely compatible'
282
- },
283
- 'h265': {
284
- 'ffmpeg_codec': 'libx265',
285
- 'description': 'H.265 (HEVC) - Better compression, smaller file size'
286
- }
287
- }
288
-
289
- async def hardmux_vid(vid_filename, sub_filename, msg, crf='23', preset='medium', resolution='original', codec='h264', fps='original', custom_filename=None, audio_bitrate='128k', audio_track='all'):
290
- chat_id = msg.chat.id
291
-
292
- # Set active process
293
- active_processes[chat_id] = 'encoding'
294
- start = time.time()
295
- out_location = None # Define outside try block for cleanup
296
-
297
- try:
298
- vid = Config.DOWNLOAD_DIR + '/' + vid_filename
299
- sub = None if not sub_filename else Config.DOWNLOAD_DIR + '/' + sub_filename
300
-
301
- # Get the base output file name
302
- out_file = '.'.join(vid_filename.split('.')[:-1])
303
-
304
- # If custom filename is provided, extract its extension
305
- if custom_filename:
306
- # Extract extension from custom filename
307
- custom_ext = custom_filename.split('.')[-1].lower()
308
- # Use the custom extension but keep the same pattern
309
- output = out_file + '_VidSub.' + custom_ext
310
- else:
311
- # Use default mp4 extension
312
- output = out_file + '_VidSub.mp4'
313
-
314
- out_location = Config.DOWNLOAD_DIR + '/' + output
315
-
316
- # Base command without filters
317
- filter_complex = ''
318
-
319
- # Add resolution scaling if not original
320
- if resolution != 'original':
321
- filter_complex = f"scale={RESOLUTIONS[resolution]}"
322
-
323
- # Add frame rate filter if not original
324
- if fps != 'original':
325
- fps_filter = f"fps={fps}"
326
- filter_complex = f"{fps_filter},{filter_complex}" if filter_complex else fps_filter
327
-
328
- # Add subtitle filter if subtitle file exists
329
- if sub:
330
- # Get subtitle extension
331
- sub_ext = sub_filename.split('.')[-1].lower()
332
-
333
- # Set up font configuration
334
- font_dir = "./fonts" # Directory containing fonts
335
- default_font = "HelveticaRounded LT Std BdCn"
336
-
337
- # Different handling based on subtitle format
338
- if sub_ext == 'ass' or sub_ext == 'ssa':
339
- subtitle_filter = f"ass='{sub}':fontsdir='{font_dir}'"
340
- else:
341
- subtitle_filter = f"subtitles='{sub}':fontsdir='{font_dir}':force_style='FontName={default_font},FontSize=18'"
342
-
343
- # Add subtitle filter to the chain
344
- filter_complex = f"{filter_complex},{subtitle_filter}" if filter_complex else subtitle_filter
345
-
346
- # Get the codec command
347
- video_codec = CODECS[codec]['ffmpeg_codec']
348
-
349
- # Base command
350
- command = [
351
- 'ffmpeg', '-hide_banner',
352
- '-fflags', '+genpts',
353
- '-i', vid,
354
- ]
355
-
356
- # Add filter complex if we have any filters
357
- if filter_complex:
358
- command.extend(['-vf', filter_complex])
359
-
360
- # Add encoding parameters
361
- command.extend([
362
- '-c:v', video_codec,
363
- '-crf', crf,
364
- '-preset', preset,
365
- '-c:a', 'aac',
366
- '-b:a', audio_bitrate,
367
- ])
368
-
369
- # Handle audio track mapping
370
- if audio_track == 'all':
371
- # Map all audio tracks
372
- command.extend([
373
- '-map', '0:v:0', # First video stream
374
- '-map', '0:a', # All audio streams
375
- ])
376
- else:
377
- # Map specific audio track (convert from 1-based to 0-based indexing)
378
- audio_stream_index = int(audio_track) - 1
379
- command.extend([
380
- '-map', '0:v:0', # First video stream
381
- '-map', f'0:a:{audio_stream_index}', # Specific audio stream
382
- ])
383
-
384
- # Add remaining parameters
385
- command.extend([
386
- '-map_metadata', '-1', # Remove all metadata
387
- '-sn', # Remove all subtitle streams
388
- ])
389
-
390
- # Add codec-specific parameters
391
- if codec == 'h265':
392
- command.extend([
393
- '-x265-params', 'log-level=error',
394
- '-tag:v', 'hvc1'
395
- ])
396
-
397
- # Add output file
398
- command.extend(['-y', out_location])
399
-
400
- process = await asyncio.create_subprocess_exec(
401
- *command,
402
- stdout=asyncio.subprocess.PIPE,
403
- stderr=asyncio.subprocess.PIPE,
404
- )
405
-
406
- await asyncio.wait([
407
- asyncio.create_task(read_stderr(start, msg, process)),
408
- asyncio.create_task(process.wait())
409
- ])
410
-
411
- if process.returncode == 0:
412
- elapsed_time = round(time.time() - start)
413
- await msg.edit(
414
- f'✅ <b>Encoding Completed Successfully!</b>\n\n'
415
- f'⏱️ Time Taken: {format_time_duration(elapsed_time)}'
416
- )
417
- await asyncio.sleep(1)
418
- return output
419
- else:
420
- await msg.edit(
421
- '❌ <b>Encoding Failed</b>\n\n'
422
- 'An error occurred during the encoding process.\n'
423
- 'Please try again or use different settings.'
424
- )
425
- # Clean up failed encoding file
426
- if out_location and os.path.exists(out_location):
427
- try:
428
- os.remove(out_location)
429
- logging.info(f"Removed failed encoding file: {output}")
430
- except Exception as e:
431
- logging.error(f"Error removing failed encoding file: {e}")
432
- return False
433
-
434
- except Exception as e:
435
- logging.error(f"Error during encoding: {e}")
436
- # Clean up failed encoding file
437
- if out_location and os.path.exists(out_location):
438
- try:
439
- os.remove(out_location)
440
- logging.info(f"Removed failed encoding file: {output}")
441
- except Exception as e:
442
- logging.error(f"Error removing failed encoding file: {e}")
443
- await msg.edit(
444
- '❌ <b>Encoding Error</b>\n\n'
445
- 'An unexpected error occurred.\n'
446
- 'Please try again or contact support.'
447
- )
448
- return False
449
- finally:
450
- # Remove active process status
451
- if chat_id in active_processes:
452
- del active_processes[chat_id]
453
-
454
- async def check_active_process(client, chat_id, message_id):
455
- """Check if user has an active process and send appropriate message"""
456
- if chat_id in active_processes:
457
- process_type = active_processes[chat_id]
458
- process_messages = {
459
- 'download': '⚠️ Please wait until the current download completes!',
460
- 'encoding': '⚠️ Please wait until the current encoding completes!',
461
- 'upload': '⚠️ Please wait until the current upload completes!'
462
- }
463
- await client.edit_message_text(
464
- chat_id=chat_id,
465
- message_id=message_id,
466
- text=process_messages.get(process_type, '⚠️ Another process is running!')
467
- )
468
- return True
469
- return False
470
-
471
- # Create a function to log and set process status
472
- def set_process_status(chat_id, status):
473
- """Set and log process status"""
474
- active_processes[chat_id] = status
475
- logging.info(f"Setting process status for {chat_id} to {status}")
476
-
477
- # Create a function to log and clear process status
478
- def clear_process_status(chat_id):
479
- """Clear and log process status"""
480
- if chat_id in active_processes:
481
- status = active_processes[chat_id]
482
- del active_processes[chat_id]
483
- logging.info(f"Cleared process status {status} for {chat_id}")
484
-
485
- # Add this function to clean up old files
486
- async def cleanup_old_files():
487
- """
488
- Recursively delete files older than 12 hours from download directory and its subdirectories.
489
- Also removes empty subdirectories after file cleanup.
490
- """
491
- while True:
492
- try:
493
- current_time = datetime.now()
494
- timeout = timedelta(hours=12)
495
- main_dir = Config.DOWNLOAD_DIR
496
-
497
- logging.info(f"Starting scheduled cleanup of files older than 12 hours")
498
-
499
- # First pass: Delete old files recursively
500
- for root, dirs, files in os.walk(main_dir, topdown=False):
501
- # Process each file in the current directory
502
- for filename in files:
503
- try:
504
- filepath = os.path.join(root, filename)
505
-
506
- # Get file's last modification time
507
- file_time = datetime.fromtimestamp(os.path.getmtime(filepath))
508
-
509
- # If file is older than 12 hours
510
- if current_time - file_time > timeout:
511
- try:
512
- os.remove(filepath)
513
- logging.info(f"Deleted old file: {filepath} (Age: {current_time - file_time})")
514
- except Exception as e:
515
- logging.error(f"Error deleting old file {filepath}: {e}")
516
- except Exception as e:
517
- logging.error(f"Error processing file {filename}: {e}")
518
-
519
- # Second pass: Remove empty subdirectories
520
- for root, dirs, files in os.walk(main_dir, topdown=False):
521
- # Skip the main download directory
522
- if root == main_dir:
523
- continue
524
-
525
- try:
526
- # Check if directory is empty
527
- if not os.listdir(root):
528
- os.rmdir(root)
529
- logging.info(f"Removed empty directory: {root}")
530
- except Exception as e:
531
- logging.error(f"Error removing empty directory {root}: {e}")
532
-
533
- logging.info(f"Completed scheduled cleanup")
534
-
535
- except Exception as e:
536
- logging.error(f"Error in cleanup task: {e}")
537
-
538
- # Run every hour
539
- await asyncio.sleep(3600)