아래 소스로 편집한 영상입니다. (음량 조절 및 이미지 추가 하였습니다.)
동영상 편집을 위한 프로그램을 만든다.
음량을 기준으로 편집한다.
사용하는 명령어는 2가지 이다.
1초마다 음량을 기록한다.
ffmpeg -i input.mp4 -af asetnsamples=44100,astats=metadata=1:reset=1,ametadata=print:key=lavfi.astats.Overall.RMS_level:file=log.txt -f null -
특정시간대별로 영상을 편집한다. (잘라서 붙인다.)
ffmpeg -i input.mp4 -filter_complex "\ [0:v]trim=0:10,setpts=PTS-STARTPTS[v0]; \ [0:a]atrim=0:10,asetpts=PTS-STARTPTS[a0]; \ [0:v]trim=20:30,setpts=PTS-STARTPTS[v1]; \ [0:a]atrim=20:30,asetpts=PTS-STARTPTS[a1]; \ [v0][a0][v1][a1]concat=n=2:v=1:a=1[outv][outa]" \ -map "[outv]" -map "[outa]" output.mp4
clojure 작성
위의 명령어를 사용하기 위한 코드를 작성한다.
네임스페이스와 경로 설정
(ns your-ns
(:require [clojure.java.io :as io]
[clojure.java.shell :refer [sh]]
[clojure.string :as str]))
(def base-path (.getPath (io/resource "cli/.temp")))
파일명 설정
(defn in-file-path [file-name]
(-> (io/file base-path file-name)
.getPath))
(defn out-file-path [file-name ext]
(let [name (-> (str/split file-name #"\.")
first)]
(-> (io/file base-path (str name "-" ext))
.getPath)))
음량 정보 출력
(defn level-file-path [file-name]
(out-file-path file-name (str "level.txt")))
(defn ffmpeg-level-cli [file-name]
(let [in-path (in-file-path file-name)
out-path (level-file-path file-name)]
["ffmpeg"
"-i" in-path
"-af" (str "asetnsamples=44100,astats=metadata=1:reset=1,ametadata=print:key=lavfi.astats.Overall.RMS_level:file=" out-path)
"-f" "null" "-"]))
(defn create-level-file [file-name]
(let [out-path (level-file-path file-name)
exist? (.exists (io/file out-path))]
(when (false? exist?)
(future
(prn "[START] create-level-file")
(apply sh (ffmpeg-level-cli file-name))
(prn "[END] create-level-file")))))
음량 정보 형태 확인
frame:0 pts:368 pts_time:0.00834467
lavfi.astats.Overall.RMS_level=-36.518740
frame:1 pts:44468 pts_time:1.00834
lavfi.astats.Overall.RMS_level=-34.606100
frame:2 pts:88568 pts_time:2.00834
lavfi.astats.Overall.RMS_level=-33.116027
...
...
...
frame, pts, pts_time, RMS_level 네 개의 정보가 두 줄로 나온다.
이를 사용하여 원하는 시간을 뽑아낸다.
먼저 파일을 읽는다.
(defn read-level-file [file-name]
(let [partitioned (->> (level-file-path file-name)
slurp
str/trim-newline
str/split-lines
(partition 2))]
(->> (map (fn [[line-1 line-2]]
(let [line (str line-1 " " line-2)
[_ frame pts time level] (re-matches #"frame:(\d+).*pts:(\d+).*pts_time:([\d\.]+).*level=([\d\.-]+).*" line)]
{(parse-long frame) (parse-double level)}))
partitioned)
(apply merge)
(into (sorted-map)))))
특정 음량을 기준으로 분리한다.
(defn valid-frame [file-name decibel {:keys [pre-frame post-frame]
:or {pre-frame 4
post-frame 2}}]
(let [level-map (read-level-file file-name)]
(->> (map (fn [[frame level]]
(when (>= level decibel)
(concat (mapv (fn [v] (- frame v)) (range 1 (inc pre-frame)))
[frame]
(mapv (fn [v] (+ frame v)) (range 1 (inc post-frame))))))
level-map)
(remove nil?)
(apply concat)
(into (sorted-set))
(remove #(> 0 %)))))
부드럽게 이어지도록 앞 4초, 뒤 2초를 추가하였다.
편집할 시간을 구한다.
(defn valid-time [file-name decibel]
(let [frame-set (valid-frame file-name decibel {})
start (first frame-set)
rest (rest frame-set)
result (reduce (fn [{:keys [start last data]
:as acc} v]
(if (= (inc last) v)
(assoc acc :last v)
(-> acc
(assoc :start v)
(assoc :last v)
(assoc :data (conj data {:start start
:end last})))))
{:start start
:last start
:data []}
rest)
result-start (:start result)
result-last (:last result)
data (:data result)]
(if (= result-start result-last)
data
(conj data {:start result-start
:end result-last}))))
편집 실행
(defn ffmpeg-filter [time-list]
(let [with-index (map-indexed (fn [index item] [index item]) time-list)
part-list (->> (mapv (fn [[index {:keys [start end]}]]
[(str "[0:v]trim=" start ":" end ",setpts=PTS-STARTPTS[v" index "]")
(str "[0:a]atrim=" start ":" end ",asetpts=PTS-STARTPTS[a" index "]")])
with-index)
(apply concat)
(str/join "; "))
part-index (->> (mapv (fn [[index _]] (str "[v" index "][a" index "]")) with-index)
(str/join ""))
part-concat (str "concat=n=" (count time-list) ":v=1:a=1[outv][outa]")]
(str part-list "; " part-index part-concat)))
(defn trim-out-path [file-name decibel]
(out-file-path file-name (str "out-db" decibel ".mp4")))
(defn ffmpeg-trim-cli [file-name decibel]
(let [time-list (valid-time file-name decibel)
in-path (in-file-path file-name)
out-path (trim-out-path file-name decibel)]
["ffmpeg"
"-i" in-path
"-filter_complex" (ffmpeg-filter time-list)
"-map" "[outv]"
"-map" "[outa]"
out-path]))
(defn trim-video [file-name decibel]
(let [total-time (time-length file-name decibel)
out-path (trim-out-path file-name decibel)
exist? (.exists (io/file out-path))]
(when (false? exist?)
(future
(-> (do (prn "[START] trim-video / total-time:" total-time " / out-file:" out-path)
(apply sh (ffmpeg-trim-cli file-name decibel))
(prn "[END] time-video"))
time)))))
'Programming > Clojure' 카테고리의 다른 글
4Clojure - #32 Duplicate a Sequence (0) | 2014.05.12 |
---|
댓글