Làm cách nào tôi có thể xóa nhiều phân đoạn khỏi video bằng FFmpeg?


51

Tôi đang cố gắng xóa một vài phần của video bằng FFmpeg.

Ví dụ, hãy tưởng tượng nếu bạn ghi lại một chương trình trên truyền hình và muốn cắt quảng cáo. Điều này là đơn giản với một trình chỉnh sửa video GUI; bạn chỉ cần đánh dấu điểm bắt đầu và kết thúc của mỗi clip sẽ bị xóa và chọn xóa. Tôi đang cố gắng làm điều tương tự từ dòng lệnh với FFmpeg.

Tôi biết cách cắt một phân đoạn thành một video mới như vậy:

ffmpeg -i input.avi -ss 00:00:20 -t 00:00:05 -map 0 -codec copy output.avi

Thao tác này sẽ cắt một clip dài năm giây và lưu nó dưới dạng tệp video mới, nhưng làm cách nào tôi có thể làm ngược lại và lưu toàn bộ video mà không có clip được chỉ định và làm cách nào tôi có thể chỉ định nhiều clip bị xóa?

Ví dụ: nếu video của tôi có thể được đại diện bởi ABCDEFG, tôi muốn tạo một video mới bao gồm ACDFG.



1
Đó không phải là những gì tôi đang hỏi, tôi muốn có được tất cả trong một "tập", nhưng điều này sẽ có các phần khác với video gốc.
Matias

@Matias, nếu bạn hỏi làm thế nào để cắt một vài clip ra khỏi video và để phần còn lại như vậy, đó sẽ là một điều, nhưng bạn muốn lấy một vài clip từ nó và kết hợp chúng với các clip từ các video khác. đây không phải là một câu hỏi riêng biệt Bạn phải làm những gì các câu hỏi khác yêu cầu để có được các phân đoạn riêng biệt, sau đó kết hợp chúng.
Synetech

1
@Synetech cảm ơn câu trả lời của bạn. Tôi không muốn kết hợp chúng với các clip từ các video khác. Tôi chỉ muốn xóa một số phần khỏi video. Ví dụ: nếu video của tôi có thể được đại diện bởi ABCDEFG, tôi muốn tạo một video mới bao gồm ACDFG.
Matias

@Synetech Đó không phải là tôi, đó là Tog, người đã phải hiểu lầm.
Matias

Câu trả lời:


47

Vâng, bạn vẫn có thể sử dụng trimbộ lọc cho điều đó. Dưới đây là một ví dụ, giả sử rằng bạn muốn cắt bỏ các phân đoạn 30-40 giây (10 giây) và 50-80 giây (30 giây):

ffmpeg -i in.ts -filter_complex \
"[0:v]trim=duration=30[a]; \
 [0:v]trim=start=40:end=50,setpts=PTS-STARTPTS[b]; \
 [a][b]concat[c]; \
 [0:v]trim=start=80,setpts=PTS-STARTPTS[d]; \
 [c][d]concat[out1]" -map [out1] out.ts

Tôi đã làm gì ở đây? Tôi đã cắt 30 giây đầu tiên, 40-50 giây và 80 giây để kết thúc và sau đó kết hợp chúng thành luồng out1với concatbộ lọc.

Giới thiệu về setpts: chúng tôi cần điều này bởi vì trim không sửa đổi thời gian hiển thị hình ảnh và khi chúng tôi cắt bộ đếm bộ giải mã 10 giây không thấy bất kỳ khung hình nào trong 10 giây này.

Nếu bạn cũng muốn có âm thanh, Bạn phải làm tương tự cho các luồng âm thanh. Vì vậy, lệnh nên là:

ffmpeg -i utv.ts -filter_complex \
"[0:v]trim=duration=30[av];[0:a]atrim=duration=30[aa];\
 [0:v]trim=start=40:end=50,setpts=PTS-STARTPTS[bv];\
 [0:a]atrim=start=40:end=50,asetpts=PTS-STARTPTS[ba];\
 [av][bv]concat[cv];[aa][ba]concat=v=0:a=1[ca];\
 [0:v]trim=start=80,setpts=PTS-STARTPTS[dv];\
 [0:a]atrim=start=80,asetpts=PTS-STARTPTS[da];\
 [cv][dv]concat[outv];[ca][da]concat=v=0:a=1[outa]" -map [outv] -map [outa] out.ts

Tuyệt vời! Tại sao chúng ta cần setpts? Bạn có thể thêm lời giải thích?
Rajib

Ok, xem câu trả lời cập nhật.
ptQa

4
+1 Điều này thực sự nên được chọn là câu trả lời. Nó phù hợp hơn cho "nhiều phân đoạn" và loại bỏ sự cần thiết phải thực hiện nhiều lệnh lần lượt. (trừ khi cách khác hiệu quả hơn về tốc độ)
Knossos

1
Làm thế nào bạn sẽ đi về việc duy trì dữ liệu phụ đề khi bạn bỏ qua khung?
Andrew Scagnelli

1
Điều này rất phi thường.
golopot

15

Tôi không bao giờ có thể làm cho giải pháp của ptQa hoạt động, chủ yếu là vì tôi không bao giờ có thể tìm ra lỗi từ các bộ lọc có nghĩa là gì hoặc cách khắc phục chúng. Giải pháp của tôi có vẻ hơi vụng về vì nó có thể để lại một mớ hỗn độn, nhưng nếu bạn ném nó vào một kịch bản, việc dọn dẹp có thể được tự động hóa. Tôi cũng thích cách tiếp cận này vì nếu có gì đó không ổn ở bước 4, bạn kết thúc với các bước hoàn thành 1-3 để phục hồi từ các lỗi hiệu quả hơn một chút.

Chiến lược cơ bản là sử dụng -t-ssđể có được video của từng phân khúc bạn muốn, sau đó kết hợp tất cả các phần cho phiên bản cuối cùng của bạn.

Giả sử bạn có 6 đoạn ABCDEF mỗi đoạn dài 5 giây và bạn muốn A (0-5 giây), C (10-15 giây) và E (20-25 giây) bạn sẽ làm điều này:

ffmpeg -i abcdef.tvshow -t 5 a.tvshow -ss 10 -t 5 c.tvshow -ss 20 -t 5 e.tvshow

hoặc là

ffmpeg -i abcdef.tvshow -t 0:00:05 a.tvshow -ss 0:00:10 -t 0:00:05 c.tvshow -ss 0:00:20 -t 0:00:05 e.tvshow

Điều đó sẽ làm cho các tệp a.tvshow, c.tvshow và e.tvshow. Đoạn phim -tcho biết mỗi clip dài bao nhiêu, vì vậy nếu c dài 30 giây, bạn có thể vượt qua trong 30 hoặc 0:00:30. Các -sstùy chọn nói bao xa để bỏ vào nguồn video, vì vậy nó luôn luôn liên quan đến sự bắt đầu của tập tin.

Sau đó, khi bạn có một loạt các tệp video, tôi tạo một tệp ace-files.txtnhư thế này:

file 'a.tvshow'
file 'c.tvshow'
file 'e.tvshow'

Lưu ý "tập tin" ở đầu và tên tập tin đã thoát sau đó.

Sau đó, lệnh:

ffmpeg -f concat -i ace-files.txt -c copy ace.tvshow

Điều đó kết hợp tất cả các tệp abe-files.txtlại với nhau, sao chép codec âm thanh và video của chúng và tạo một tệp ace.tvshowchỉ là phần a, c và e. Sau đó chỉ cần nhớ để xóa ace-files.txt, a.tvshow, c.tvshowe.tvshow.

Tuyên bố miễn trừ trách nhiệm : Tôi không biết làm thế nào (trong) hiệu quả này được so sánh với các phương pháp khác về mặt ffmpegnhưng đối với mục đích của tôi, nó hoạt động tốt hơn. Hy vọng nó sẽ giúp được ai đó.


4
điều này rất dễ hiểu đối với những người mới như tôi, cảm ơn
juanpastas

4
Lưu ý nếu bạn đang sử dụng bash, bạn có thể tránh việc tạo tệp ( ace-files.txttrong ví dụ của bạn) với:ffmpeg -f concat -i <(printf "file '%s'\n" $(pwd)/prefix_*.tvshow) -c copy output.tvshow
bufh

Điều này thực sự hữu ích nhưng tôi đã phải xóa các trích dẫn từ các tên tệp xung quanh hoặc nó không hoạt động.
tchaymore

Khi nối các video, có một video đen ngắn và âm thanh im lặng giữa chúng, khoảng 25ms. Bất kỳ ý tưởng để thoát khỏi điều đó?
Antonio Bonifati 'Farmboy'

Hmm ... Tôi chỉ kiểm tra toán học của bạn về thời gian bù và bỏ qua, và đảm bảo rằng bạn sẽ không để lại một khoảng cách 25ms ... Tôi chưa từng trải qua điều đó.
xbakesx

5

Tôi đã tạo một kịch bản để tăng tốc độ chỉnh sửa TV đã ghi. Kịch bản hỏi bạn về thời gian bắt đầu và kết thúc của các phân đoạn bạn muốn giữ và chia chúng ra thành các tệp. Nó cung cấp cho bạn các tùy chọn, bạn có thể:

  • Lấy một hoặc nhiều phân đoạn.
  • Bạn có thể kết hợp các phân đoạn thành một tệp kết quả.
  • Sau khi tham gia bạn có thể giữ hoặc xóa các tập tin phần.
  • Bạn có thể giữ tệp gốc hoặc thay thế nó bằng tệp mới của bạn.

Video của nó trong hành động: ở đây

Cho tôi biết bạn nghĩ gì.

 #!/bin/bash
/bin/date >>segmenter.log

function segment (){
while true; do
    echo "Would you like to cut out a segment ?"
    echo -e "1) Yes\n2) No\n3) Quit"
    read CHOICE
    if [ "$CHOICE" == "3" ]; then
        exit
    elif [ "$CHOICE" == "2" ]; then
        clear
        break
    elif [ "$CHOICE" == "1" ]; then
        clear
        ((segments++))
        echo "What time does segment $segments start ?"
        read SEGSTART
        clear
        echo -e "Segment $segments start set to $SEGSTART\n"  
        echo "What time does segment $segments end ?"
        read SEGEND
        clear
        echo -e "Segment $segments end set to $SEGEND\n"
        break
    else
        clear
        echo -e "Bad option"
        segment "$segments"
    fi
done
if [ "$CHOICE" == "1" ]; then
    echo "Cutting file $file video segment $segments starting at $SEGSTART and ending at $SEGEND"
    ffmpeg -i "$file" -ss $SEGSTART -to  $SEGEND -map 0:0 -map 0:1 -c:a copy -c:v copy  "$filename-part$segments.$extension"  >> segmenter.log 2>&1
    clear
    echo -e "Cut file $filename-part$segments.$extension starting at $SEGSTART and ending at $SEGEND\n"                             
    segment "$segments"
fi
}

file="$1"
filename="${file%.*}"
extension="${file##*.}"
clear
segments=0
segment "$segments"
clear
if (("$segments"==1)); then
mv $filename"-part1."$extension "$filename-segmented.$extension"
elif (("$segments">1)); then
echo "Would you like to join the segments into one file ?"      
       OPTIONS="Yes No Quit"
       select opt in $OPTIONS; do
       clear
        if [ "$opt" == "Quit" ]; then
            exit
        elif [ "$opt" == "Yes" ]; then
            clear
            echo "Joining segments"
            ffmpeg -f concat -i <(for f in $filename"-part"*$extension;         do echo "file '$(pwd)/$f'"; done) -c:a copy -c:v copy "$filename-segmented.$extension" >>         segmenter.log 2>&1
            clear
            echo "Would you like to delete the part files ?"
            select opt in $OPTIONS; do
            clear
            if [ "$opt" == "Quit" ]; then
                exit
            elif [ "$opt" == "Yes" ]; then
                for f in $filename"-part"*$extension; do rm $f; done
                break
            elif [ "$opt" == "No" ]; then
                break
            else
                clear
                echo -e "Bad option\n"
            fi
            done
            break
        clear
        elif [ "$opt" == "No" ]; then
            exit
        else
            clear
            echo -e "Bad option\n"
        fi
    done
fi
echo "Would you like to replace the original file with the result of your changes ?"
OPTIONS="Yes No Quit"
select opt in $OPTIONS; do
    clear
    if [ "$opt" == "Quit" ]; then
        exit
    elif [ "$opt" == "Yes" ]; then
        rm $file
        mv "$filename-segmented.$extension" $file
        break
    elif [ "$opt" == "No" ]; then
        break
    else
        clear
        echo -e "Bad option\n"
    fi
done

2
Kịch bản này là tuyệt vời! Chỉ cần một sửa đổi vì bạn đang sử dụng các đường dẫn tuyệt đối để ghép nối: add safe=0, tức là ffmpeg -f concat -safe 0 -i ...Xem ffmpeg.org/ffmpeg-all.html#Options-36
pkSML

2

Mặc dù câu trả lời được cung cấp bởi ptQa dường như có hiệu quả, tôi đã phát triển một giải pháp khác có thể hoạt động tốt.

Về cơ bản, những gì tôi làm là cắt một video cho mỗi phần của video gốc mà tôi muốn đưa vào kết quả của mình. Sau đó, tôi nối chúng với Demuxer concat giải thích ở đây .

Giải pháp tương tự như tôi đã thử đầu tiên và trình bày các vấn đề đồng bộ hóa. Những gì tôi đã thêm là lệnh -avoid_negative_ts 1 khi tạo các video khác nhau. Với giải pháp này, các vấn đề đồng bộ biến mất.


1

Đối với những người gặp rắc rối theo cách tiếp cận của ptQa, có một cách hợp lý hơn một chút để đi về nó. Thay vì nối từng bước trên đường đi, chỉ cần làm tất cả ở cuối.

Đối với mỗi đầu vào, hãy xác định cặp A / V:

//Input1:
[0:v]trim=start=10:end=20,setpts=PTS-STARTPTS,format=yuv420p[0v];
[0:a]atrim=start=10:end=10,asetpts=PTS-STARTPTS[0a];
//Input2:
[0:v]trim=start=30:end=40,setpts=PTS-STARTPTS,format=yuv420p[1v];
[0:a]atrim=start=30:end=40,asetpts=PTS-STARTPTS[1a];
//Input3:
[0:v]trim=start=30:end=40,setpts=PTS-STARTPTS,format=yuv420p[2v];
[0:a]atrim=start=30:end=40,asetpts=PTS-STARTPTS[2a];

Xác định số lượng cặp bạn cần, sau đó ghép tất cả chúng trong một lần, trong đó n = tổng số đầu vào.

[0v][0a][1v][1a][2v][2a]concat=n=3:v=1:a=1[outv][outa] -map [outv] -map [outa] out.mp4

Điều này có thể dễ dàng được xây dựng trong một vòng lặp.

Một lệnh hoàn chỉnh sử dụng 2 đầu vào có thể trông như thế này:

 -y -i in.mp4 -filter_complex 
[0:v]trim=start=10.0:end=15.0,setpts=PTS-STARTPTS,format=yuv420p[0v];
[0:a]atrim=start=10.0:end=15.0,asetpts=PTS-STARTPTS[0a];
[0:v]trim=start=65.0:end=70.0,setpts=PTS-STARTPTS,format=yuv420p[1v];
[0:a]atrim=start=65.0:end=70.0,asetpts=PTS-STARTPTS[1a];[0v][0a][1v]
[1a]concat=n=2:v=1:a=1[outv][outa] -map [outv] -map [outa] out.mp4
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.