jhj0517 commited on
Commit
4596138
1 Parent(s): a1c5f04

Add `VideoInfo` and `create_video_from_frames`

Browse files
Files changed (1) hide show
  1. modules/video_utils.py +126 -16
modules/video_utils.py CHANGED
@@ -3,13 +3,23 @@ import os
3
  from typing import List, Optional, Union
4
  from PIL import Image
5
  import numpy as np
 
 
6
 
7
  from modules.logger_util import get_logger
8
- from modules.paths import TEMP_DIR
9
 
10
  logger = get_logger()
11
 
12
 
 
 
 
 
 
 
 
 
13
  def extract_frames(
14
  vid_input: str,
15
  output_temp_dir: str = TEMP_DIR,
@@ -60,11 +70,116 @@ def extract_sound(
60
  subprocess.run(command, check=True)
61
  except subprocess.CalledProcessError as e:
62
  logger.exception("Error occurred while extracting sound from the video")
63
- raise RuntimeError(f"An error occurred: {str(e)}")
64
 
65
  return output_path
66
 
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  def get_frames_from_dir(vid_dir: str,
69
  available_extensions: Optional[Union[List, str]] = None,
70
  as_numpy: bool = False) -> List:
@@ -98,25 +213,20 @@ def clean_temp_dir(temp_dir: str = TEMP_DIR):
98
 
99
  def clean_sound_files(sound_dir: str):
100
  """Removes all sound files from the directory."""
101
- sound_extensions = ('.mp3', '.wav', '.aac', '.flac', '.ogg', '.m4a', '.wma')
102
-
103
- for filename in os.listdir(sound_dir):
104
- if filename.lower().endswith(sound_extensions):
105
- file_path = os.path.join(sound_dir, filename)
106
- try:
107
- os.remove(file_path)
108
- except Exception as e:
109
- logger.exception("Error while removing sound files")
110
- raise RuntimeError(f"Error removing {file_path}: {str(e)}")
111
 
112
 
113
  def clean_image_files(image_dir: str):
114
  """Removes all image files from the dir"""
115
- image_extensions = ('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp')
 
 
116
 
117
- for filename in os.listdir(image_dir):
118
- if filename.lower().endswith(image_extensions):
119
- file_path = os.path.join(image_dir, filename)
 
120
  try:
121
  os.remove(file_path)
122
  except Exception as e:
 
3
  from typing import List, Optional, Union
4
  from PIL import Image
5
  import numpy as np
6
+ from dataclasses import dataclass
7
+ import re
8
 
9
  from modules.logger_util import get_logger
10
+ from modules.paths import TEMP_DIR, TEMP_OUT_DIR
11
 
12
  logger = get_logger()
13
 
14
 
15
+ @dataclass
16
+ class VideoInfo:
17
+ num_frames: Optional[int] = None
18
+ frame_rate: Optional[int] = None
19
+ duration: Optional[float] = None
20
+ has_sound: Optional[bool] = None
21
+
22
+
23
  def extract_frames(
24
  vid_input: str,
25
  output_temp_dir: str = TEMP_DIR,
 
70
  subprocess.run(command, check=True)
71
  except subprocess.CalledProcessError as e:
72
  logger.exception("Error occurred while extracting sound from the video")
 
73
 
74
  return output_path
75
 
76
 
77
+ def get_video_info(vid_input: str) -> VideoInfo:
78
+ """
79
+ Extract video information using ffmpeg.
80
+ """
81
+ command = [
82
+ 'ffmpeg',
83
+ '-i', vid_input,
84
+ '-map', '0:v:0',
85
+ '-c', 'copy',
86
+ '-f', 'null',
87
+ '-'
88
+ ]
89
+
90
+ try:
91
+ result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
92
+ encoding='utf-8', errors='replace', check=True)
93
+ output = result.stderr
94
+
95
+ num_frames = None
96
+ frame_rate = None
97
+ duration = None
98
+ has_sound = False
99
+
100
+ for line in output.splitlines():
101
+ if 'Stream #0:0' in line and 'Video:' in line:
102
+ fps_match = re.search(r'(\d+(?:\.\d+)?) fps', line)
103
+ if fps_match:
104
+ frame_rate = float(fps_match.group(1))
105
+
106
+ elif 'Duration:' in line:
107
+ duration_match = re.search(r'Duration: (\d{2}):(\d{2}):(\d{2}\.\d{2})', line)
108
+ if duration_match:
109
+ h, m, s = map(float, duration_match.groups())
110
+ duration = h * 3600 + m * 60 + s
111
+
112
+ elif 'Stream' in line and 'Audio:' in line:
113
+ has_sound = True
114
+
115
+ if frame_rate and duration:
116
+ num_frames = int(frame_rate * duration)
117
+
118
+ return VideoInfo(
119
+ num_frames=num_frames,
120
+ frame_rate=frame_rate,
121
+ duration=duration,
122
+ has_sound=has_sound
123
+ )
124
+
125
+ except subprocess.CalledProcessError as e:
126
+ logger.exception("Error occurred while getting info from the video")
127
+ return VideoInfo()
128
+
129
+
130
+ def create_video_from_frames(
131
+ frames_dir: str,
132
+ frame_rate: Optional[int] = None,
133
+ sound_path: Optional[str] = None,
134
+ output_dir: Optional[str] = None,
135
+ ):
136
+ """
137
+ Create a video from frames and save it to the output_path. This needs FFmpeg installed.
138
+ """
139
+ if not os.path.exists(frames_dir):
140
+ raise "frames_dir does not exist"
141
+
142
+ if output_dir is None:
143
+ output_dir = TEMP_OUT_DIR
144
+ num_files = len(os.listdir(output_dir))
145
+ filename = f"{num_files:05d}.mp4"
146
+ output_path = os.path.join(output_dir, filename)
147
+
148
+ if sound_path is None:
149
+ temp_sound = os.path.join(TEMP_DIR, "sound.mp3")
150
+ if os.path.exists(temp_sound):
151
+ sound_path = temp_sound
152
+
153
+ if frame_rate is None:
154
+ frame_rate = 25 # Default frame rate
155
+
156
+ command = [
157
+ 'ffmpeg',
158
+ '-y',
159
+ '-framerate', frame_rate,
160
+ '-i', os.path.join(frames_dir, "%05d.jpg"),
161
+ '-c:v', 'libx264',
162
+ '-pix_fmt', 'yuv420p',
163
+ output_path
164
+ ]
165
+
166
+ if sound_path is not None:
167
+ command += [
168
+ '-i', sound_path,
169
+ '-c:a', 'aac',
170
+ '-strict', 'experimental',
171
+ '-b:a', '192k',
172
+ '-shortest'
173
+ ]
174
+ print(command)
175
+ try:
176
+ subprocess.run(command, check=True)
177
+ except subprocess.CalledProcessError as e:
178
+ logger.exception("Error occurred while creating video from frames")
179
+ print("Done, output path:", output_path)
180
+ return output_path
181
+
182
+
183
  def get_frames_from_dir(vid_dir: str,
184
  available_extensions: Optional[Union[List, str]] = None,
185
  as_numpy: bool = False) -> List:
 
213
 
214
  def clean_sound_files(sound_dir: str):
215
  """Removes all sound files from the directory."""
216
+ sound_extensions = ['.mp3', '.wav', '.aac', '.flac', '.ogg', '.m4a', '.wma']
217
+ _clean_files_with_extension(sound_dir, sound_extensions)
 
 
 
 
 
 
 
 
218
 
219
 
220
  def clean_image_files(image_dir: str):
221
  """Removes all image files from the dir"""
222
+ image_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp']
223
+ _clean_files_with_extension(image_dir, image_extensions)
224
+
225
 
226
+ def _clean_files_with_extension(dir_path: str, extensions: List):
227
+ for filename in os.listdir(dir_path):
228
+ if filename.lower().endswith(tuple(extensions)):
229
+ file_path = os.path.join(dir_path, filename)
230
  try:
231
  os.remove(file_path)
232
  except Exception as e: