ฉันจะใช้ ffmpeg เพื่อแยกวิดีโอ MPEG เป็นชิ้น ๆ 10 นาทีได้อย่างไร


63

มักจะมีความต้องการในโอเพนซอร์สหรือชุมชนนักพัฒนาที่ใช้งานเพื่อเผยแพร่กลุ่มวิดีโอขนาดใหญ่ออนไลน์ (วิดีโอนัดพบค่ายพูดคุยเกี่ยวกับเทคโนโลยี ... ) การเป็นฉันเป็นนักพัฒนาและไม่ใช่ช่างวิดีโอฉันไม่มีความปรารถนาที่จะแยกส่วนเสริมในบัญชี Vimeo พรีเมี่ยม ฉันจะนำวิดีโอพูดคุยเกี่ยวกับเทคโนโลยี MPEG ขนาด 12.5 GB (1:20:00) มาแบ่งเป็น 00:10:00 เพื่อให้ง่ายต่อการอัพโหลดไปยังเว็บไซต์แบ่งปันวิดีโอ


ขอขอบคุณเป็นพิเศษที่ @StevenD สำหรับรองรับแท็กใหม่
กาเบรียล

คำตอบ:


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
...

การรวมสิ่งนี้เข้ากับสคริปต์เพื่อทำแบบวนซ้ำจะไม่ยาก

ระวังว่าถ้าคุณพยายามคำนวณจำนวนการวนซ้ำโดยอิงจากเอาต์พุตระยะเวลาจากการffprobeโทรซึ่งประมาณจากอัตราบิตเฉลี่ยที่จุดเริ่มต้นของคลิปและขนาดไฟล์ของคลิปยกเว้นว่าคุณให้-count_framesอาร์กิวเมนต์ซึ่งทำให้การทำงานช้าลงอย่างมาก .

สิ่งที่จะตระหนักถึงก็คือว่าตำแหน่งของ-ssตัวเลือกในบรรทัดคำสั่งเรื่อง ที่ฉันมีตอนนี้มันช้า แต่แม่นยำ รุ่นแรกของคำตอบนี้ให้ทางเลือกที่รวดเร็ว แต่ไม่ถูกต้อง บทความที่เชื่อมโยงนี้ยังอธิบายถึงทางเลือกที่รวดเร็วและแม่นยำ แต่ส่วนใหญ่ซึ่งคุณจ่ายด้วยความซับซ้อนเล็กน้อย

ทั้งหมดที่นอกเหนือฉันไม่คิดว่าคุณต้องการที่จะตัด 10 นาทีสำหรับแต่ละคลิป นั่นจะทำให้บาดแผลถูกต้องตรงกลางประโยคแม้แต่คำพูด ฉันคิดว่าคุณควรใช้โปรแกรมตัดต่อวิดีโอหรือเครื่องเล่นเพื่อหาจุดตัดตามธรรมชาติห่างกันเพียง 10 นาที

สมมติว่าไฟล์ของคุณอยู่ในรูปแบบที่ YouTube สามารถยอมรับได้โดยตรงคุณไม่จำเป็นต้องเข้ารหัสใหม่เพื่อรับเซ็กเมนต์ เพียงผ่านจุดตัดตามธรรมชาติเพื่อffmpegบอกให้ผ่านการเข้ารหัส A / V ผ่านการแตะต้องโดยใช้ตัวแปลงสัญญาณ "คัดลอก":

$ 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
...

-c copyอาร์กิวเมนต์บอกเพื่อคัดลอกลำธารป้อนข้อมูลทั้งหมด (วิดีโอ, เสียง, และอาจคนอื่น ๆ เช่นคำบรรยาย) เข้าออกตามที่เป็น สำหรับโปรแกรม A / V ง่ายๆก็เทียบเท่ากับธงละเอียดเพิ่มเติมหรือธงแบบเก่า-c:v copy -c:a copy -vcodec copy -acodec copyคุณจะใช้สไตล์ verbose มากขึ้นเมื่อคุณต้องการคัดลอกสตรีมเดียวเท่านั้น แต่เข้ารหัสอีกชุดหนึ่ง ยกตัวอย่างเช่นหลายปีที่ผ่านมามีการปฏิบัติร่วมกันกับไฟล์ QuickTime ในการบีบอัดวิดีโอที่มีวิดีโอ H.264 แต่ปล่อยให้เสียงเป็นPCM ไม่มีการบีบอัด ; หากคุณเจอไฟล์ดังกล่าววันนี้คุณสามารถทำให้มันทันสมัยขึ้นด้วย-c:v copy -c:a aacการประมวลผลเฉพาะสตรีมเสียงโดยปล่อยให้วิดีโอไม่ถูกแตะต้อง

จุดเริ่มต้นสำหรับทุกคำสั่งด้านบนหลังจากจุดแรกคือจุดเริ่มต้นของคำสั่งก่อนหน้าบวกกับระยะเวลาของคำสั่งก่อนหน้า


1
"การตัดที่ 10 นาทีสำหรับแต่ละคลิป" เป็นจุดที่ดี
Chris

อาจจะใช้ -show_packets param คุณสามารถทำให้มันแม่นยำยิ่งขึ้น
rogerdpack

วิธีที่จะนำไปสู่วง?
kRazzy R

i ใช้คำสั่งนี้แยกออกเป็นขวา แต่ตอนนี้วิดีโอและเสียงไม่ได้อยู่ในระบบ SYNC สำหรับความช่วยเหลือใด ๆ
Sunil Chaudhary

@SunilChaudhary: รูปแบบไฟล์ A / V นั้นซับซ้อนและไม่ใช่ทุกซอฟต์แวร์ที่ใช้วิธีเดียวกันดังนั้นข้อมูลที่ffmpegจำเป็นในการรักษาการประสานระหว่างกระแสข้อมูลเสียงและวิดีโออาจไม่มีอยู่ในไฟล์ต้นฉบับ หากสิ่งนี้เกิดขึ้นกับคุณมีวิธีการแยกที่ซับซ้อนมากขึ้นเพื่อหลีกเลี่ยงปัญหา
Warren Young

53

นี่คือวิธีแก้ปัญหาหนึ่งบรรทัด :

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

โปรดทราบว่าสิ่งนี้ไม่ได้ให้การแยกที่ถูกต้อง แต่ควรเหมาะสมกับความต้องการของคุณ มันจะถูกตัดที่เฟรมแรกแทนหลังจากเวลาที่ระบุไว้segment_timeในโค้ดด้านบนมันจะเป็นหลังเครื่องหมาย 20 นาที

หากคุณพบว่าสามารถเล่นได้เฉพาะก้อนแรกเท่านั้นให้ลองเพิ่ม-reset_timestamps 1ตามที่ระบุไว้ในความคิดเห็น

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

2
มันจริงจะช่วยให้คุณมากแยกที่ถูกต้องถ้าคุณมีค่าคุณภาพของวิดีโอ แทนที่จะแยกตามเวลาเฉพาะจะแยกคีย์เฟรมที่ใกล้ที่สุดตามเวลาที่ร้องขอดังนั้นแต่ละเซ็กเมนต์ใหม่จะเริ่มต้นด้วยคีย์เฟรมเสมอ
Malvineous

3
หน่วยคืออะไร 8s? 8min? 8H?
user1133275

1
@ user1133275 ครั้งที่สอง
Jon

2
สำหรับ Mac ฉันพบว่าสิ่งนี้ส่งผลให้มีวิดีโอ N เอาต์พุต แต่หนึ่งในนั้นคือ MP4 ที่ถูกต้องและสามารถดูได้ ชิ้น N-1 อื่น ๆ เป็นวิดีโอเปล่า (สีดำทั้งหมด) โดยไม่มีเสียง เพื่อให้ทำงานได้ฉันต้องเพิ่มการตั้งค่าสถานะ reset_timestamps ดังนี้: ffmpeg -i input.mp4 -c copy -map 0 -segment_time 8 -f เซกเมนต์ -reset_timestamps 1 เอาท์พุท% 03d.mp4
jarmod

3
พบว่าการเพิ่มการ-reset_timestamps 1แก้ไขปัญหาสำหรับฉัน
jlarsch

6

ประสบปัญหาเดียวกันก่อนหน้านี้และรวบรวมสคริปต์ Python แบบง่าย ๆ เพื่อทำสิ่งนั้น (ใช้ FFMpeg) วางจำหน่ายแล้วที่นี่: https://github.com/c0decracker/video-splitterและวางด้านล่าง:

#!/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
ครั้งต่อไปกรุณาทำในความคิดเห็น คำตอบแบบลิงก์เท่านั้นไม่ได้ถูกใจที่นี่จริง ๆ และเหมือนกันถ้าคุณโฆษณาไซต์ของคุณ หากเป็นโครงการโอเพ่นซอร์สที่มีซอร์สโค้ดอาจเป็นข้อยกเว้น แต่ตอนนี้ฉันเสี่ยงต่อสิทธิ์การตรวจสอบของฉันโดยไม่ลงคะแนนให้ลบคำตอบของคุณ และใช่คุณไม่สามารถโพสต์ความคิดเห็น แต่หลังจากที่คุณรวบรวม 5 upvotes (ซึ่งดูเหมือนว่าเร็วมากในกรณีของคุณ) คุณจะ
user259412

2
สวัสดีและยินดีต้อนรับสู่เว็บไซต์ กรุณาอย่าโพสต์ลิงค์คำตอบเท่านั้น ในขณะที่สคริปต์ของคุณอาจจะให้คำตอบที่ดีลิงค์ไปยังมันไม่ใช่คำตอบ มันเป็นป้ายบอกทางชี้ไปที่คำตอบ เพิ่มเติมเกี่ยวกับที่นี่ เนื่องจากคุณได้ให้ลิงค์ฉันไปข้างหน้าและรวมสคริปต์ไว้ในเนื้อหาของคำตอบของคุณ หากคุณคัดค้านโปรดลบคำตอบทั้งหมด
terdon

4

หากคุณต้องการสร้างชิ้นส่วนที่เหมือนกันจริงๆจะต้องบังคับให้ ffmpeg สร้าง i-frame บนเฟรมแรกของทุกชิ้นเพื่อให้คุณสามารถใช้คำสั่งนี้เพื่อสร้างชิ้นที่ 0.5 วินาที

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

นี่ควรเป็นคำตอบที่ยอมรับได้ ขอบคุณที่แบ่งปันเพื่อน!
Stephan Ahlf

4

สำรองเพิ่มเติมอ่านวิธีที่จะเป็น

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
*/

นี่คือซอร์สและรายการของคำสั่ง FFmpeg ที่ใช้ทั่วไป


3

หมายเหตุ: -ss mm:ss.xxxเครื่องหมายวรรคตอนที่แน่นอนของรูปแบบทางเลือกคือ ฉันพยายามชั่วโมงพยายามที่จะใช้งานง่าย แต่ผิดmm:ss:xxจะไม่มีประโยชน์

$ man ffmpeg | grep -C1 position

ตำแหน่ง -ss
แสวงหาตำแหน่งเวลาที่กำหนดในไม่กี่วินาที รองรับ "ไวยากรณ์ hh: mm: ss [.xxx]"

อ้างอิงที่นี่และที่นี่


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

เพียงใช้สิ่งที่สร้างไว้ใน ffmpeg เพื่อทำสิ่งนี้

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

สิ่งนี้จะแบ่งออกเป็น 2 chucks โดยประมาณแบ่งเป็น keyframes ที่เกี่ยวข้องและจะส่งออกไปยังไฟล์ cam_out_h2640001.mp4, cam_out_h2640002.mp4 เป็นต้น


ไม่แน่ใจว่าฉันเข้าใจว่าทำไมคำตอบนี้จึงถูกโหวต ดูเหมือนว่าจะทำงานได้ดี
Costin
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.