understanding commited on
Commit
3fd6c41
·
verified ·
1 Parent(s): 4f88652

Create utils/ffmpeg.py

Browse files
Files changed (1) hide show
  1. utils/ffmpeg.py +83 -0
utils/ffmpeg.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # filename: utils/ffmpeg.py
2
+
3
+ import asyncio
4
+ import logging
5
+ import os
6
+
7
+ import config
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ async def _run_ffmpeg_command(command: list[str]) -> bool:
12
+ """A helper function to run an FFMPEG command asynchronously."""
13
+ if not config.ENABLE_FFMPEG:
14
+ logger.warning("FFMPEG is disabled in config, skipping command.")
15
+ return False
16
+
17
+ process = await asyncio.create_subprocess_exec(
18
+ *command,
19
+ stdout=asyncio.subprocess.PIPE,
20
+ stderr=asyncio.subprocess.PIPE
21
+ )
22
+
23
+ stdout, stderr = await process.communicate()
24
+
25
+ if process.returncode != 0:
26
+ logger.error(f"FFMPEG command failed with exit code {process.returncode}")
27
+ logger.error(f"Stderr: {stderr.decode(errors='ignore').strip()}")
28
+ return False
29
+
30
+ return True
31
+
32
+ async def generate_thumbnail(video_path: str, thumbnail_path: str) -> str | None:
33
+ """
34
+ Generates a thumbnail from a video file.
35
+ Returns the path to the thumbnail on success, None on failure.
36
+ """
37
+ logger.info(f"Generating thumbnail for {video_path}...")
38
+
39
+ # Command to seek 5 seconds into the video, grab one frame, resize it, and save as jpg
40
+ command = [
41
+ "ffmpeg",
42
+ "-i", video_path,
43
+ "-ss", "00:00:05", # Seek to 5-second mark
44
+ "-vframes", "1", # Extract only one frame
45
+ "-vf", "scale=320:-1", # Resize width to 320px, maintain aspect ratio
46
+ "-q:v", "3", # Image quality (1-5, lower is better)
47
+ "-y", # Overwrite output file if it exists
48
+ thumbnail_path
49
+ ]
50
+
51
+ success = await _run_ffmpeg_command(command)
52
+
53
+ if success and os.path.exists(thumbnail_path):
54
+ logger.info(f"Thumbnail generated successfully: {thumbnail_path}")
55
+ return thumbnail_path
56
+ else:
57
+ logger.error(f"Failed to generate thumbnail for {video_path}")
58
+ return None
59
+
60
+ async def remux_to_mp4(input_path: str, output_path: str) -> str | None:
61
+ """
62
+ Remuxes a video file (e.g., MKV) into an MP4 container without re-encoding.
63
+ This is a very fast operation.
64
+ Returns the path to the new MP4 file on success, None on failure.
65
+ """
66
+ logger.info(f"Remuxing {input_path} to MP4 format...")
67
+
68
+ command = [
69
+ "ffmpeg",
70
+ "-i", input_path,
71
+ "-c", "copy", # Copy all streams (video, audio, subtitles) without re-encoding
72
+ "-y", # Overwrite output file
73
+ output_path
74
+ ]
75
+
76
+ success = await _run_ffmpeg_command(command)
77
+
78
+ if success and os.path.exists(output_path):
79
+ logger.info(f"Remuxed successfully: {output_path}")
80
+ return output_path
81
+ else:
82
+ logger.error(f"Failed to remux {input_path}")
83
+ return None