【问题标题】:Is it possible to use FFmpeg to cut "random" sections from a folder of videos and concat them into 1 video?是否可以使用 FFmpeg 从视频文件夹中剪切“随机”部分并将它们连接成 1 个视频?
【发布时间】:2019-04-15 20:43:17
【问题描述】:

我意识到这听起来是一个简单的问题,而且之前已经回答过了。但是,我似乎找不到一个脚本可以读取不同长度的视频文件夹,从每个视频中复制一个随机片段,并将它们连接成一个视频。

一个例子:

我有一个包含 150 个视频的文件夹,标签为 Fashion-Setlist-01.mp4、Fashion-Setlist-02.mp4 等。 每个都超过1小时。我想从每个视频中随机抽取一个 10 秒的部分,然后将它们随机添加在一起,从而生成一个视频。只需几个视频,这似乎很容易,但计划是从潜在的 100 个视频中读取。也应该可以从每个视频中提取多个部分。如果视频需要更长,我想我们可以为更多片段运行两次脚本。

【问题讨论】:

  • 我认为这种问题通常可以通过从头开始编写管道来解决。通过管道,我不是(必然)指 bash 管道,我指的是一系列脚本,每个脚本执行一个中间步骤并将输出存储在一个临时目录中。该序列中的后续脚本将处理他们找到的任何中间/临时文件。这有助于将其分解为可管理的步骤。例如,首先您创建一个脚本,该脚本仅执行从文件名参数中抓取随机视频片段并将其存储在临时目录中的一项任务。一个 makefile 可以协调这个过程。

标签: python linux bash video ffmpeg


【解决方案1】:

编辑:使用 ffmpeg 剪切视频需要在关键帧上完成。我广泛编辑了这段代码,首先找到关键帧,然后绕过它。它对我有用。

所以要在bash 中执行此操作,假设存在一些程序randtime.py 以“H:MM:SS”格式输出随机开始时间,以及一些其他程序在给定时间附近找到视频关键帧,这是一个快速破解版:

#! /usr/bin/env bash

CUTLIST=cut_list.txt
RANDTIME=~/Bin/randtime.py
KEYFRAMER=~/Bin/find_next_key_frame.py

count=0
echo "" > "$CUTLIST"
for file in *.mp4
do
    count=$(( $count + 1 ));
    outfile="cut_$count.mp4"
    start_time=`python "$RANDTIME"`
    # Find the next keyframe, at or after the random time
    start_keyframe_time=`$KEYFRAMER "$file" "$start_time"`
    if [ $? -eq 0 ]; then
        echo "Nearest keyframe to \"$start_time\" is \"$start_keyframe_time\""
        echo "ffmpeg -loglevel quiet -y -i \"$file\" -ss $start_keyframe_time -t 00:00:10 \"$outfile\""
        ffmpeg -loglevel quiet -y -i "$file" -ss $start_keyframe_time -t 00:00:10 "$outfile"
        if [ $? -ne 0 ]; then
            echo "ffmpeg returned an error on [$file], aborting"
        #   exit 1
        fi
        echo "file '$outfile'" >> "$CUTLIST"
    else
        echo "ffprobe found no suitable key-frame near \"$start_time\""
    fi
done

echo "Concatenating ... "
cat "$CUTLIST"

ffmpeg -f concat -i cut_list.txt -c copy all_cuts.mp4

if [ -f "$CUTLIST" ]; then
    rm "$CUTLIST"
fi

还有随机时间,在python:

#! /usr/bin/env python3

import random

#TODO: ensure we're at least 8 seconds before 1 hour

hrs = 0 # random.randint(0,1)
mns = random.randint(0,59)
scs = random.randint(0,59)

print("%d:%02d:%02d" % (hrs,mns,scs))

然后,在python 中再次找到关键帧,正好在给定的时间之后。

#! /usr/bin/env python3

import sys
import subprocess
import os
import os.path

FFPROBE='/usr/bin/ffprobe'

EXE=sys.argv[0].replace('\\','/').split('/')[-1]

if (not os.path.isfile(FFPROBE)):
    sys.stderr.write("%s: The \"ffprobe\" part of FFMPEG seems to be missing\n" % (EXE))
    sys.exit(1)

if (len(sys.argv) == 1 or (len(sys.argv)==2 and sys.argv[1] in ('--help', '-h', '-?', '/?'))):
    sys.stderr.write("%s: Give video filename and time as arguments\n" % (EXE))
    sys.stderr.write("%s:     e.g.: video_file.mp4 0:25:14 \n" % (EXE))
    sys.stderr.write("%s: Outputs the next keyframe at or after the given time\n" % (EXE))
    sys.exit(1)

VIDEO_FILE = sys.argv[1]
FRAME_TIME = sys.argv[2].strip()

if (not os.path.isfile(VIDEO_FILE)):
    sys.stderr.write("%s: The vdeo file \"%s\" seems to be missing\n" % (EXE, VIDEO_FILE))
    sys.exit(1)


### Launch FFMPEG's ffprobe to identify the frames
command = "ffprobe -show_frames -pretty \"%s\"" % VIDEO_FILE
frame_list = subprocess.getoutput(command)

### The list of frames is a huge bunch of lines like:
###    [FRAME]
###    media_type=video
###    key_frame=0
###    best_effort_timestamp=153088
###    best_effort_timestamp_time=0:00:09.966667
###    pkt_duration_time=0:00:00.033333
###    height=360
###    ...
###    [/FRAME]

### Parse the stats about each frame, keeping only the Video Keyframes
key_frames = []
for frame in frame_list.split("[FRAME]"):
    # split the frame lines up into separate "x=y" pairs
    frame_dict = {}
    frame_vars = frame.replace('\r', '\n').replace("\n\n", '\n').split('\n')
    for frame_pair in frame_vars:
        if (frame_pair.find('=') != -1):
            try:
                var,value = frame_pair.split('=', 1)
                frame_dict[var.strip()] = value.strip()
            except:
                sys.stderr.write("%s: Warning: Unable to parse [%s]\n" % (EXE, frame_pair))
        # Do we want to keep this frame?
        # we want video frames, that are key frames
        if ("media_type", "key_frame" in frame_dict and frame_dict["media_type"] == "video" and frame_dict["key_frame"] == "1"):
            key_frames.append(frame_dict)

### Throw away duplicates, ans sort (why are there duplicates?)
key_frame_list = set()
for frame_dict in key_frames:        
    #print(str(frame_dict))
    if ("best_effort_timestamp_time" in frame_dict):
        key_frame_list.add(frame_dict["best_effort_timestamp_time"])
key_frame_list = list(key_frame_list)
key_frame_list.sort()
sys.stderr.write("Keyframes found: %u, from %s -> %s\n" % (len(key_frame_list), key_frame_list[0], key_frame_list[-1]))

### Find the same, or next-larger keyframe
found = False
for frame_time in key_frame_list:
    #sys.stderr.write("COMPARE %s > %s\n" % (frame_time , FRAME_TIME))
    if (frame_time > FRAME_TIME):
        print(frame_time)
        found = True
        break  # THERE CAN BE ONLY ONE!

### Failed?  Print something possibly useful
if (found == False):
    sys.stderr.write("%s: Warning: No keyframe found\n" % (EXE))
    print("0:00:00")
    sys.exit(-1)
else:
    sys.exit(0)  # All'swell

【讨论】:

  • 这不会开始剪切视频关键帧,我认为这会提供更好的输出。见 - stackoverflow.com/questions/14005110/…
  • 感谢您的回复!我遇到了这个。 “输入 #0,concat,来自 'cut_list.txt':持续时间:N/A,比特率:N/A 输出 #0,mp4,到 'all_cuts.mp4':输出文件 #0 不包含任何流”' 编辑:似乎只有 4 个文件中有视频。其余的每个大约 464 字节。
  • @EmmyStrand - 看起来 ffmpeg 只能在关键帧(或类似)处剪切。也许有一个选项可以告诉 ffmpeg 在给定时间后的下一个关键帧处开始剪切,但如果有我不知道是否它,所以我强制它。 find_next_key_frame.py 需要一段时间才能运行。这是次优的。
  • 谢谢!!我认为它有效。调整代码以使每个 mp4 随机剪切 2 次会有多难?或者将脚本运行两次而不连接第一次运行会更容易吗?
  • @EmmyStrand - 如果您在输入文件集中指定了两次相同的文件,它将从每个文件中进行两次剪切。虽然它是随机的,所以它们可能会重叠;)
【解决方案2】:

moviepy 是最合适的工具(它使用 ffmpeg 作为后端)。在moviepy中连接视频很简单:

import moviepy.editor
import os
import random
import fnmatch 

directory = '/directory/to/videos/'
xdim = 854
ydim = 480
ext = "*mp4"
length = 10

outputs=[]

# compile list of videos
inputs = [os.path.join(directory,f) for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f)) and fnmatch.fnmatch(f, ext)]

for i in inputs:

    # import to moviepy
    clip = moviepy.editor.VideoFileClip(i).resize( (xdim, ydim) ) 

    # select a random time point
    start = round(random.uniform(0,clip.duration-length), 2) 

    # cut a subclip
    out_clip = clip.subclip(start,start+length)

    outputs.append(out_clip)

# combine clips from different videos
collage = moviepy.editor.concatenate_videoclips(outputs) 

collage.write_videofile('out.mp4')

【讨论】:

  • 谢谢@hb_!我会试一试。
  • EDIT*** 这很好用!!它正在做我需要的。 1 个问题,它是否从每个视频中提取超过 1 个子剪辑?如果没有,这没什么大不了的,我只是好奇。
  • 很高兴为您提供帮助。上面的代码从每个视频中提取一个剪辑,但如果您需要另一个,您可以复制 for 循环中的行。或者,更动态的解决方案是将其放入您正在寻找的最小总长度的 while 循环中。
猜你喜欢
  • 2017-07-08
  • 2018-11-08
  • 2018-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-30
相关资源
最近更新 更多