Skip to content

Commit 4486ab7

Browse files
authored
Merge pull request #50 from ai-forever/video-cut
Video cut
2 parents 75f2d09 + 99f6c6f commit 4486ab7

File tree

2 files changed

+43
-5
lines changed

2 files changed

+43
-5
lines changed

DPF/transforms/video_ffmpeg_transforms.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,27 @@ def __init__(
3434
resizer: Optional[Resizer] = None,
3535
fps: Optional[int] = None,
3636
fps_eps: float = 0.1,
37+
cut_start_col: Optional[str] = None,
38+
cut_duration_col: Optional[str] = None,
39+
copy_stream: bool = False,
40+
preset: Optional[str] = None,
41+
crf: Optional[int] = None,
42+
copy_audio_stream: bool = True,
3743
pool_type: PoolOptions = 'threads',
3844
workers: int = 16,
3945
pbar: bool = True,
40-
preset: Optional[str] = None,
41-
crf: Optional[int] = None,
42-
copy_audio_stream: bool = True
4346
):
4447
super().__init__(pool_type, workers, pbar)
4548
self.resizer = resizer
4649
self.fps = fps
4750
self.fps_eps = fps_eps
51+
self.cut_start_col = cut_start_col
52+
self.cut_duration_col = cut_duration_col
53+
if self.cut_duration_col or self.cut_start_col:
54+
assert self.cut_duration_col and self.cut_start_col, f"Both {self.cut_duration_col} and {self.cut_start_col} must be specified"
55+
self.copy_when_cut = copy_stream
56+
if self.copy_when_cut:
57+
assert self.copy_when_cut and not (self.fps or self.resizer), "Copy stream can be used only for cutting videos"
4858

4959
self.preset = preset
5060
self.crf = crf
@@ -53,7 +63,7 @@ def __init__(
5363
self.default_args = ' '.join(self.get_default_ffmpeg_args())
5464

5565
assert is_ffmpeg_installed(), "Install ffmpeg first"
56-
assert self.resizer or self.fps, "At least one transform should be specified"
66+
assert self.resizer or self.fps or self.cut_start_col, "At least one transform should be specified"
5767

5868
def get_default_ffmpeg_args(self) -> list[str]:
5969
args = []
@@ -72,6 +82,8 @@ def required_metadata(self) -> list[str]:
7282
meta += ['width', 'height']
7383
if self.fps:
7484
meta += ['fps']
85+
if self.cut_duration_col and self.cut_start_col:
86+
meta += [self.cut_start_col, self.cut_duration_col]
7587
return meta
7688

7789
@property
@@ -90,6 +102,7 @@ def modality(self) -> str:
90102
def _process_filepath(self, data: TransformsFileData) -> TransformsFileData:
91103
filepath = data.filepath
92104
ext = filepath.split('.')[-1]
105+
ffmpeg_args_start: list[str] = []
93106
ffmpeg_args_map: dict[str, list[str]] = {}
94107
result_metadata: dict[str, Any] = {}
95108

@@ -110,10 +123,20 @@ def _process_filepath(self, data: TransformsFileData) -> TransformsFileData:
110123
video_fps = float(self.fps)
111124
result_metadata['fps'] = video_fps
112125

126+
if self.cut_start_col and self.cut_duration_col and data.metadata[self.cut_start_col] is not None:
127+
start = data.metadata[self.cut_start_col]
128+
cut_duration = data.metadata[self.cut_duration_col]
129+
ffmpeg_args_start.append(f'-ss {start}')
130+
ffmpeg_args_map['-t'] = [str(cut_duration)]
131+
if self.copy_when_cut:
132+
ffmpeg_args_map['-c'] = ['copy']
133+
ffmpeg_args_map['-avoid_negative_ts'] = ['1']
134+
113135
if len(ffmpeg_args_map) > 0:
114136
args_str = convert_ffmpeg_args_to_str(ffmpeg_args_map)
137+
args_start_str = ' '.join(ffmpeg_args_start)
115138
temp_filename = str(uuid.uuid4()) + '.' + ext
116-
ffmpeg_command = f'ffmpeg -hide_banner -i {filepath} {args_str} {self.default_args} {temp_filename} -y'
139+
ffmpeg_command = f'ffmpeg -hide_banner {args_start_str} -i {filepath} {args_str} {self.default_args} {temp_filename} -y'
117140
subprocess.run(ffmpeg_command, shell=True, capture_output=True, check=True)
118141
shutil.move(temp_filename, filepath)
119142

docs/transforms.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,19 @@ transforms = VideoFFMPEGTransforms(
3838
workers=8
3939
)
4040
processor.apply_transform(transforms)
41+
```
42+
43+
Fast video cutting:
44+
_cut_start_ and _cut_duration_ columns should have values in seconds.
45+
For example, cutting video from 7 to 11 second should be specified in dataframe as: _cut_start_ = 7, _cut_duration_ = 4
46+
```python
47+
from DPF.transforms import VideoFFMPEGTransforms
48+
49+
transforms = VideoFFMPEGTransforms(
50+
cut_start_col='cut_start',
51+
cut_duration_col='cut_duration',
52+
copy_stream=True,
53+
workers=4,
54+
)
55+
processor.apply_transform(transforms)
4156
```

0 commit comments

Comments
 (0)