Làm cách nào tôi có thể sử dụng ffmpeg để chia video MPEG thành các đoạn 10 phút?


63

Thường có nhu cầu trong cộng đồng nhà phát triển nguồn mở hoặc hoạt động để xuất bản các phân đoạn video lớn trực tuyến. (Video gặp gỡ, cắm trại, nói chuyện về công nghệ ...) Tôi là một nhà phát triển và không phải là nhà quay phim, tôi không muốn bỏ thêm tiền vào tài khoản Vimeo cao cấp. Sau đó, làm cách nào để tôi có một video nói chuyện công nghệ MPEG 12,5 GB (1:20:00) và cắt nó thành các đoạn 00:10:00 để dễ dàng tải lên các trang web chia sẻ video?


Đặc biệt cảm ơn @StevenD vì đã cung cấp các thẻ mới.
Gabriel

Câu trả lời:


69
$ ffmpeg -i source-file.foo -ss 0 -t 600 first-10-min.m4v
$ ffmpeg -i source-file.foo -ss 600 -t 600 second-10-min.m4v
$ ffmpeg -i source-file.foo -ss 1200 -t 600 third-10-min.m4v
...

Gói nó thành một kịch bản để thực hiện nó trong một vòng lặp sẽ không khó.

Xin lưu ý rằng nếu bạn cố gắng tính số lần lặp dựa trên đầu ra thời lượng từ một ffprobecuộc gọi thì điều này được ước tính từ tốc độ bit trung bình khi bắt đầu clip và kích thước tệp của clip trừ khi bạn đưa ra -count_framesđối số, làm chậm hoạt động của nó .

Một điều cần lưu ý là vị trí của -sstùy chọn trên dòng lệnh có vấn đề . Nơi tôi có nó bây giờ là chậm nhưng chính xác. Phiên bản đầu tiên của câu trả lời này đã đưa ra sự thay thế nhanh chóng nhưng không chính xác . Bài viết được liên kết cũng mô tả một sự thay thế chủ yếu là nhanh nhưng vẫn chính xác, mà bạn phải trả tiền với một chút phức tạp.

Ngoài ra, tôi không nghĩ bạn thực sự muốn cắt đúng 10 phút cho mỗi clip. Điều đó sẽ đặt các vết cắt ngay giữa câu, thậm chí từ. Tôi nghĩ bạn nên sử dụng trình chỉnh sửa video hoặc trình phát để tìm các điểm cắt tự nhiên chỉ cách nhau 10 phút.

Giả sử tệp của bạn ở định dạng mà YouTube có thể chấp nhận trực tiếp, bạn không phải mã hóa lại để có được phân khúc. Chỉ cần chuyển các điểm cắt điểm tự nhiên sang ffmpeg, yêu cầu nó chuyển A / V được mã hóa thông qua chưa được xử lý bằng cách sử dụng codec "sao chép":

$ ffmpeg -i source.m4v -ss 0 -t 593.3 -c copy part1.m4v
$ ffmpeg -i source.m4v -ss 593.3 -t 551.64 -c copy part2.m4v
$ ffmpeg -i source.m4v -ss 1144.94 -t 581.25 -c copy part3.m4v
...

Đối -c copysố yêu cầu nó sao chép tất cả các luồng đầu vào (âm thanh, video và các tiềm năng khác, như phụ đề) vào đầu ra như hiện trạng. Đối với các chương trình A / V đơn giản, nó tương đương với các cờ dài hơn -c:v copy -c:a copyhoặc các cờ kiểu cũ -vcodec copy -acodec copy. Bạn sẽ sử dụng kiểu dài dòng hơn khi bạn chỉ muốn sao chép một trong các luồng, nhưng mã hóa lại luồng khác. Ví dụ, nhiều năm trước đây có một thực tế phổ biến với các tệp QuickTime để nén video bằng video H.264 nhưng để lại âm thanh là PCM không nén ; nếu bạn chạy trên một tệp như vậy ngày hôm nay, bạn có thể hiện đại hóa nó -c:v copy -c:a aacđể xử lý lại luồng âm thanh, khiến video không bị ảnh hưởng.

Điểm bắt đầu cho mọi lệnh ở trên sau lệnh đầu tiên là điểm bắt đầu của lệnh trước cộng với thời lượng của lệnh trước đó.


1
"Cắt đúng 10 phút cho mỗi clip" là một điểm tốt.
Chris

có thể bằng cách sử dụng param -show_packets bạn có thể làm cho nó chính xác hơn.
rogerdpack

Làm thế nào để đặt ở trên trong một vòng lặp?
kRazzy R

i Sử dụng cmd này, nó được chia thành đúng nhưng Bây giờ video và âm thanh không có trên SYNC bất kỳ trợ giúp nào
Sunil Chaudhary

@SunilChaudhary: Các định dạng tệp A / V rất phức tạp và không phải tất cả phần mềm đều thực hiện chúng theo cùng một cách, do đó thông tin ffmpegcần duy trì đồng bộ hóa giữa các luồng âm thanh và video có thể không có trong tệp nguồn. Nếu điều này xảy ra với bạn, có nhiều phương pháp phân tách phức tạp hơn để tránh vấn đề.
Warren Young

53

Đây là giải pháp một dòng :

ffmpeg -i input.mp4 -c copy -map 0 -segment_time 00:20:00 -f segment output%03d.mp4

Xin lưu ý rằng điều này không cung cấp cho bạn các phân chia chính xác, nhưng phải phù hợp với nhu cầu của bạn. Thay vào đó, nó sẽ cắt ở khung đầu tiên sau thời gian được chỉ định sau segment_time, trong đoạn mã trên sẽ là sau mốc 20 phút.

Nếu bạn thấy rằng chỉ có đoạn đầu tiên có thể chơi được, hãy thử thêm -reset_timestamps 1như được đề cập trong các bình luận.

ffmpeg -i input.mp4 -c copy -map 0 -segment_time 00:20:00 -f segment -reset_timestamps 1 output%03d.mp4

2
Nó thực sự mang lại cho bạn sự phân chia rất chính xác, nếu bạn coi trọng chất lượng video. Thay vì phân tách dựa trên một thời gian cụ thể, nó sẽ phân tách trên khung hình chính gần nhất theo thời gian được yêu cầu, vì vậy mỗi phân đoạn mới luôn bắt đầu bằng một khung hình chính.
Malvineous

3
các đơn vị là gì? 8 giây? 8 phút? 8 giờ?
dùng1133275

1
@ user1133275 lần thứ hai
Jon

2
Trên Mac, tôi thấy rằng điều này dẫn đến các đoạn video đầu ra N nhưng chỉ có đoạn đầu tiên trong số đó là MP4 hợp lệ, có thể xem được. Các đoạn N-1 khác là video trống (toàn màu đen) không có âm thanh. Để làm cho nó hoạt động, tôi cần thêm cờ reset_timestamp như vậy: ffmpeg -i input.mp4 -c copy -map 0 -seribution_time 8 -f phân đoạn -reset_timestamp 1 đầu ra% 03d.mp4.
ngày

3
thấy rằng việc thêm -reset_timestamps 1khắc phục sự cố cho tôi
jlarsch

6

Đối mặt với cùng một vấn đề trước đó và tập hợp một tập lệnh Python đơn giản để làm việc đó (sử dụng FFMpeg). Có sẵn tại đây: https://github.com/c0decracker/video-splitter và dán bên dưới:

#!/usr/bin/env python
import subprocess
import re
import math
from optparse import OptionParser
length_regexp = 'Duration: (\d{2}):(\d{2}):(\d{2})\.\d+,'
re_length = re.compile(length_regexp)
def main():
    (filename, split_length) = parse_options()
    if split_length <= 0:
        print "Split length can't be 0"
        raise SystemExit
    output = subprocess.Popen("ffmpeg -i '"+filename+"' 2>&1 | grep 'Duration'",
                              shell = True,
                              stdout = subprocess.PIPE
    ).stdout.read()
    print output
    matches = re_length.search(output)
    if matches:
        video_length = int(matches.group(1)) * 3600 + \
                       int(matches.group(2)) * 60 + \
                       int(matches.group(3))
        print "Video length in seconds: "+str(video_length)
    else:
        print "Can't determine video length."
        raise SystemExit
    split_count = int(math.ceil(video_length/float(split_length)))
    if(split_count == 1):
        print "Video length is less then the target split length."
        raise SystemExit
    split_cmd = "ffmpeg -i '"+filename+"' -vcodec copy "
    for n in range(0, split_count):
        split_str = ""
        if n == 0:
            split_start = 0
        else:
            split_start = split_length * n
            split_str += " -ss "+str(split_start)+" -t "+str(split_length) + \
                         " '"+filename[:-4] + "-" + str(n) + "." + filename[-3:] + \
                         "'"
    print "About to run: "+split_cmd+split_str
    output = subprocess.Popen(split_cmd+split_str, shell = True, stdout =
                              subprocess.PIPE).stdout.read()
def parse_options():
    parser = OptionParser()
    parser.add_option("-f", "--file",
                      dest = "filename",
                      help = "file to split, for example sample.avi",
                      type = "string",
                      action = "store"
    )
    parser.add_option("-s", "--split-size",
                      dest = "split_size",
                      help = "split or chunk size in seconds, for example 10",
                      type = "int",
                      action = "store"
    )
    (options, args) = parser.parse_args()
    if options.filename and options.split_size:
        return (options.filename, options.split_size)
    else:
        parser.print_help()
        raise SystemExit
if __name__ == '__main__':
    try:
        main()
    except Exception, e:
        print "Exception occured running main():"
        print str(e)

1
Lần sau làm điều này xin vui lòng trong một bình luận. Câu trả lời chỉ liên kết không thực sự thích ở đây và tương tự nếu bạn quảng cáo trang web của mình. Nếu đó là một dự án mã nguồn mở với mã nguồn, có thể đó là một ngoại lệ, nhưng bây giờ tôi đã mạo hiểm các đặc quyền xem xét của mình bằng cách không bỏ phiếu cho việc xóa câu trả lời của bạn. Và vâng, bạn không thể đăng bình luận, nhưng sau khi bạn thu thập được 5 lượt upvote (có vẻ rất nhanh trong trường hợp của bạn), bạn sẽ làm được.
dùng259412

2
Xin chào và chào mừng đến với trang web. Xin vui lòng không gửi liên kết chỉ câu trả lời. Trong khi chính kịch bản của bạn có thể sẽ tạo ra một câu trả lời tuyệt vời, một liên kết đến nó không phải là một câu trả lời. Đó là một biển chỉ dẫn để trả lời. Thêm về điều đó ở đây . Vì bạn vui lòng đưa ra liên kết, tôi đã tiếp tục và đưa kịch bản vào phần câu trả lời của bạn. Nếu bạn phản đối điều đó, xin vui lòng xóa câu trả lời hoàn toàn.
terdon

4

Nếu bạn muốn tạo Chunks thực sự giống nhau, phải buộc ffmpeg tạo i-frame trên khung đầu tiên của mỗi khối để bạn có thể sử dụng lệnh này để tạo 0,5 giây.

ffmpeg -hide_banner  -err_detect ignore_err -i input.mp4 -r 24 -codec:v libx264  -vsync 1  -codec:a aac  -ac 2  -ar 48k  -f segment   -preset fast  -segment_format mpegts  -segment_time 0.5 -force_key_frames  "expr: gte(t, n_forced * 0.5)" out%d.mkv

Đây phải là câu trả lời được chấp nhận. Cảm ơn đã chia sẻ bạn đời!
Stephan Ahlf

4

Một cách khác dễ đọc hơn sẽ là

ffmpeg -i input.mp4 -ss 00:00:00 -to 00:10:00 -c copy output1.mp4
ffmpeg -i input.mp4 -ss 00:10:00 -to 00:20:00 -c copy output2.mp4

/**
* -i  input file
* -ss start time in seconds or in hh:mm:ss
* -to end time in seconds or in hh:mm:ss
* -c codec to use
*/

Đây là nguồn và danh sách các lệnh FFmpeg thường được sử dụng.


3

Lưu ý dấu câu chính xác của định dạng thay thế là -ss mm:ss.xxx. Tôi đã vật lộn hàng giờ để cố gắng sử dụng trực quan nhưng sai nhưng mm:ss:xxkhông có kết quả.

$ man ffmpeg | grep -C1 position

-ss vị trí
Tìm kiếm vị trí thời gian nhất định trong vài giây. Cú pháp "hh: mm: ss [.xxx]" cũng được hỗ trợ.

Tài liệu tham khảo ở đâyđây .


0
#!/bin/bash

if [ "X$1" == "X" ]; then
    echo "No file name for split, exiting ..."
    exit 1
fi

if [ ! -f "$1" ]; then
    echo "The file '$1' doesn't exist. exiting ..."
    exit 1
fi

duration=$(ffmpeg -i "$1" 2>&1 | grep Duration | sed 's/^.*Duration: \(.*\)\..., start.*$/\1/' | awk -F: '{ print ($1 * 3600) + ($2 * 60) + $3 }')      #'
split_time=${split_time:-55}
time=0
part=1

filename=${file%%.*}
postfix=${file##*.}

while [ ${time} -le ${duration} ]; do

echo    ffmpeg -i "$1" -vcodec copy -ss ${time} -t ${split_time} "${filename}-${part}.${postfix}"
    (( part++ ))
    (( time = time + split_time ))

done

0

Chỉ cần sử dụng những gì được tích hợp vào ffmpeg để làm chính xác điều này.

ffmpeg -i invid.mp4 -threads 3 -vcodec copy -f segment -segment_time 2 cam_out_h264%04d.mp4

Điều này sẽ phân chia nó thành khoảng 2 giây, chia nhỏ tại các khung hình chính có liên quan và sẽ xuất ra các tệp cam thừng_h2640001.mp4, cam thừng_h2640002.mp4, v.v.


Không chắc chắn tôi hiểu tại sao câu trả lời này được bỏ phiếu. Nó dường như làm việc ổn.
Costin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.