Skip to content

Videoadjust

contrast_brightness_ffmpeg

contrast_brightness_ffmpeg(filename, contrast=0, brightness=0, target_name=None, overwrite=True)

Applies contrast and brightness adjustments on the source video using ffmpeg.

Parameters:

Name Type Description Default
filename str

Path to the video to process.

required
contrast int / float

Increase or decrease contrast. Values range from -100 to 100. Defaults to 0.

0
brightness int / float

Increase or decrease brightness. Values range from -100 to 100. Defaults to 0.

0
target_name str

Defaults to None (which assumes that the input filename with the suffix "_cb" should be used).

None
overwrite bool

Whether to allow overwriting existing files or to automatically increment target filename to avoid overwriting. Defaults to True.

True

Returns:

Name Type Description
str

Path to the output video.

Source code in musicalgestures/_videoadjust.py
 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
def contrast_brightness_ffmpeg(filename, contrast=0, brightness=0, target_name=None, overwrite=True):
    """
    Applies contrast and brightness adjustments on the source video using ffmpeg.

    Args:
        filename (str): Path to the video to process.
        contrast (int/float, optional): Increase or decrease contrast. Values range from -100 to 100. Defaults to 0.
        brightness (int/float, optional): Increase or decrease brightness. Values range from -100 to 100. Defaults to 0.
        target_name (str, optional): Defaults to None (which assumes that the input filename with the suffix "_cb" should be used).
        overwrite (bool, optional): Whether to allow overwriting existing files or to automatically increment target filename to avoid overwriting. Defaults to True.

    Returns:
        str: Path to the output video.
    """
    if contrast == 0 and brightness == 0:
        return

    of, fex = os.path.splitext(filename)

    if target_name is None:
        target_name = of + '_cb' + fex
    if not overwrite:
        target_name = generate_outfilename(target_name)

    # keeping values in sensible range
    contrast = np.clip(contrast, -100.0, 100.0)
    brightness = np.clip(brightness, -100.0, 100.0)

    # ranges are "handpicked" so that the results are close to the results of contrast_brightness_cv2 (deprecated)
    if contrast == 0:
        p_saturation, p_contrast, p_brightness = 0, 0, 0
    elif contrast > 0:
        p_saturation = scale_num(contrast, 0, 100, 1, 1.9)
        p_contrast = scale_num(contrast, 0, 100, 1, 2.3)
        p_brightness = scale_num(contrast, 0, 100, 0, 0.04)
    elif contrast < 0:
        p_saturation = scale_num(contrast, 0, -100, 1, 0)
        p_contrast = scale_num(contrast, 0, -100, 1, 0)
        p_brightness = 0

    if brightness != 0:
        p_brightness += brightness / 100

    cmd = ['ffmpeg', '-y', '-i', filename, '-vf',
           f'eq=saturation={p_saturation}:contrast={p_contrast}:brightness={p_brightness}', '-q:v', '3', "-c:a", "copy", target_name]

    ffmpeg_cmd(cmd, get_length(filename),
               pb_prefix='Adjusting contrast and brightness:')

    return target_name

skip_frames_ffmpeg

skip_frames_ffmpeg(filename, skip=0, target_name=None, overwrite=True)

Time-shrinks the video by skipping (discarding) every n frames determined by skip. To discard half of the frames (ie. double the speed of the video) use skip=1.

Parameters:

Name Type Description Default
filename str

Path to the video to process.

required
skip int

Discard skip frames before keeping one. Defaults to 0.

0
target_name str

Defaults to None (which assumes that the input filename with the suffix "_skip" should be used).

None
overwrite bool

Whether to allow overwriting existing files or to automatically increment target filename to avoid overwriting. Defaults to True.

True

Returns:

Name Type Description
str

Path to the output video.

Source code in musicalgestures/_videoadjust.py
 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
def skip_frames_ffmpeg(filename, skip=0, target_name=None, overwrite=True):
    """
    Time-shrinks the video by skipping (discarding) every n frames determined by `skip`.
    To discard half of the frames (ie. double the speed of the video) use `skip=1`.

    Args:
        filename (str): Path to the video to process.
        skip (int, optional): Discard `skip` frames before keeping one. Defaults to 0.
        target_name (str, optional): Defaults to None (which assumes that the input filename with the suffix "_skip" should be used).
        overwrite (bool, optional): Whether to allow overwriting existing files or to automatically increment target filename to avoid overwriting. Defaults to True.

    Returns:
        str: Path to the output video.
    """
    if skip == 0:
        return

    of, fex = os.path.splitext(filename)
    fex = '.avi'

    pts_ratio = 1 / (skip+1)
    atempo_ratio = skip+1

    if target_name is None:
        target_name = _safe_output_name(of + '_skip' + fex)
    else:
        target_name = _safe_output_name(target_name)
    if not overwrite:
        target_name = generate_outfilename(target_name)

    # original duration of the file is stored in the -metadata title variable
    if has_audio(filename):
        # atempo only accepts values in [0.5, 100.0] per filter; chain multiple for large ratios
        atempo_chain = _build_atempo_chain(atempo_ratio)
        cmd = ['ffmpeg', '-y', '-i', filename, '-metadata', f'title={get_length(filename)}', '-filter_complex',
               f'[0:v]setpts={pts_ratio}*PTS[v];[0:a]{atempo_chain}[a]', '-map', '[v]', '-map', '[a]', '-q:v', '3', '-shortest', target_name]
    else:
        cmd = ['ffmpeg', '-y', '-i', filename, '-metadata', f'title={get_length(filename)}', '-filter_complex',
               f'[0:v]setpts={pts_ratio}*PTS[v]', '-map', '[v]', '-q:v', '3', target_name]

    ffmpeg_cmd(cmd, get_length(filename), pb_prefix='Skipping frames:')

    return target_name

fixed_frames_ffmpeg

fixed_frames_ffmpeg(filename, frames=0, target_name=None, overwrite=True)

Specify a fixed target number frames to extract from the video. To extract only keyframes from the video, set the parameter keyframes to True.

Parameters:

Name Type Description Default
filename str

Path to the video to process.

required
frames int)

Number frames to extract from the video. If set to -1, it will only extract the keyframes of the video. Defaults to 0.

0
target_name str

Defaults to None (which assumes that the input filename with the suffix "_fixed" should be used).

None
overwrite bool

Whether to allow overwriting existing files or to automatically increment target filename to avoid overwriting. Defaults to True.

True

Returns:

Name Type Description
str

Path to the output video.

Source code in musicalgestures/_videoadjust.py
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
def fixed_frames_ffmpeg(filename, frames=0, target_name=None, overwrite=True):
    """
    Specify a fixed target number frames to extract from the video. 
    To extract only keyframes from the video, set the parameter keyframes to True.

    Args:
        filename (str): Path to the video to process.
        frames (int), optional): Number frames to extract from the video. If set to -1, it will only extract the keyframes of the video. Defaults to 0.
        target_name (str, optional): Defaults to None (which assumes that the input filename with the suffix "_fixed" should be used).
        overwrite (bool, optional): Whether to allow overwriting existing files or to automatically increment target filename to avoid overwriting. Defaults to True.

    Returns:
        str: Path to the output video.
    """
    of, fex = os.path.splitext(filename)

    if fex != '.mp4':
        # Convert video to mp4
        filename = convert_to_mp4(of + fex, overwrite=overwrite)
        of, fex = os.path.splitext(filename)

    if target_name is None:
         target_name = of + '_fixed' + fex
    if not overwrite:
        target_name = generate_outfilename(target_name)

    cap = cv2.VideoCapture(filename)
    nb_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    fps = int(cap.get(cv2.CAP_PROP_FPS))

    pts_ratio = frames / nb_frames
    atempo_ratio = 1 / pts_ratio

    if frames == 0:
        return

    # Extract only keyframes
    if frames == -1:
        cmd = ['ffmpeg', '-y', '-discard', 'nokey', '-i', filename, '-c', 'copy', 'temp.h264'] 
        ffmpeg_cmd(cmd, get_length(filename), pb_prefix='Extracting keyframes:')
        cmd = ['ffmpeg', '-y', '-r', str(fps), '-f', 'h264', '-i', 'temp.h264', '-c', 'copy', target_name]
        ffmpeg_cmd(cmd, get_length(filename), pb_prefix='Encoding temporary video file:') 
        # Remove temporary h264 video file
        os.remove('temp.h264')

        return target_name

    if has_audio(filename):
        atempo_chain = _build_atempo_chain(atempo_ratio)
        cmd = ['ffmpeg', '-y', '-i', filename, '-filter_complex',
               f'[0:v]setpts={pts_ratio}*PTS[v];[0:a]{atempo_chain}[a]', '-map', '[v]', '-map', '[a]', '-q:v', '3', '-shortest', target_name]
    else:
        cmd = ['ffmpeg', '-y', '-i', filename, '-filter_complex',
               f'[0:v]setpts={pts_ratio}*PTS[v]', '-map', '[v]', '-q:v', '3', target_name]

    ffmpeg_cmd(cmd, get_length(filename), pb_prefix='Fixing frames:')

    return target_name

mg_resample

mg_resample(self, fps=None, speed=None, skip=None, target_name=None, overwrite=True)

Resample the (already loaded) video and return a new MgVideo, leaving the original object untouched.

Three independent, combinable operations:

  • fps: retime to a target frame rate using FFmpeg's fps filter — duration-preserving (frames are dropped/duplicated to hit the rate), e.g. 30 → 25 fps.
  • speed: change playback speed by a factor (>1 faster/shorter, <1 slower/longer); the video is retimed with setpts and the audio with atempo so they stay in sync.
  • skip: integer frame decimation — discard skip frames for every one kept (this also shortens/speeds up the clip), matching the loader's skip parameter.

When more than one is given they are applied in order: skipspeed/fps.

Parameters:

Name Type Description Default
fps float

Target frame rate (duration-preserving). Defaults to None.

None
speed float

Playback-speed factor. Defaults to None.

None
skip int

Discard skip frames for every one kept. Defaults to None.

None
target_name str

Output name. Defaults to None (input filename + "_resampled").

None
overwrite bool

Overwrite or auto-increment the filename. Defaults to True.

True

Returns:

Name Type Description
MgVideo MgVideo

a new MgVideo pointing to the resampled file.

Source code in musicalgestures/_videoadjust.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
def mg_resample(self, fps=None, speed=None, skip=None, target_name=None, overwrite=True) -> "musicalgestures.MgVideo":
    """
    Resample the (already loaded) video and return a **new** MgVideo, leaving the original
    object untouched.

    Three independent, combinable operations:

    * ``fps``: retime to a target frame rate using FFmpeg's ``fps`` filter — **duration-preserving**
      (frames are dropped/duplicated to hit the rate), e.g. 30 → 25 fps.
    * ``speed``: change playback speed by a factor (>1 faster/shorter, <1 slower/longer); the video
      is retimed with ``setpts`` and the audio with ``atempo`` so they stay in sync.
    * ``skip``: integer frame decimation — discard ``skip`` frames for every one kept (this also
      shortens/speeds up the clip), matching the loader's ``skip`` parameter.

    When more than one is given they are applied in order: ``skip`` → ``speed``/``fps``.

    Args:
        fps (float, optional): Target frame rate (duration-preserving). Defaults to None.
        speed (float, optional): Playback-speed factor. Defaults to None.
        skip (int, optional): Discard ``skip`` frames for every one kept. Defaults to None.
        target_name (str, optional): Output name. Defaults to None (input filename + "_resampled").
        overwrite (bool, optional): Overwrite or auto-increment the filename. Defaults to True.

    Returns:
        MgVideo: a new MgVideo pointing to the resampled file.
    """
    import musicalgestures

    if fps is None and speed is None and not skip:
        raise ValueError("Provide at least one of fps, speed, or skip.")
    if speed is not None and speed <= 0:
        raise ValueError("speed must be a positive factor (e.g. 2.0 = twice as fast).")
    if fps is not None and fps <= 0:
        raise ValueError("fps must be a positive number.")

    source = self.filename

    # 1) Integer frame decimation (also speeds up) — reuse the tested helper.
    if skip:
        source = skip_frames_ffmpeg(source, int(skip), overwrite=overwrite)

    # 2) Speed and/or frame-rate retime in a single FFmpeg pass.
    final = source
    if speed is not None or fps is not None:
        of, fex = os.path.splitext(source)
        if target_name is None:
            out = of + '_resampled' + fex
        else:
            out = os.path.splitext(target_name)[0] + fex
        if not overwrite:
            out = generate_outfilename(out)

        vfilters = []
        if speed is not None and speed != 1:
            vfilters.append(f'setpts=PTS/{speed:.6g}')
        if fps is not None:
            vfilters.append(f'fps={fps:.6g}')
        vf = ','.join(vfilters)

        if speed is not None and speed != 1 and has_audio(source):
            # Retime audio too so it stays in sync with the sped-up/slowed-down video.
            atempo_chain = _build_atempo_chain(speed)
            cmd = ['ffmpeg', '-y', '-i', source, '-filter_complex',
                   f'[0:v]{vf}[v];[0:a]{atempo_chain}[a]', '-map', '[v]', '-map', '[a]',
                   '-q:v', '3', '-shortest', out]
        else:
            # fps-only (or no audio): -vf retimes the video and passes audio through unchanged.
            cmd = ['ffmpeg', '-y', '-i', source, '-vf', vf, '-q:v', '3', out]

        ffmpeg_cmd(cmd, get_length(source), pb_prefix='Resampling:')
        final = out

    return musicalgestures.MgVideo(final, color=self.color, returned_by_process=True)