diff options
Diffstat (limited to 'emacs/.emacs.d/lisp/my/my-media-segment.el')
-rw-r--r-- | emacs/.emacs.d/lisp/my/my-media-segment.el | 123 |
1 files changed, 107 insertions, 16 deletions
diff --git a/emacs/.emacs.d/lisp/my/my-media-segment.el b/emacs/.emacs.d/lisp/my/my-media-segment.el index f222316..e8ee5cc 100644 --- a/emacs/.emacs.d/lisp/my/my-media-segment.el +++ b/emacs/.emacs.d/lisp/my/my-media-segment.el @@ -50,18 +50,93 @@ The process can be started by applying 'start-process' on START-PROCESS-ARGS." (when my-media-segment-queued-jobs (funcall (pop my-media-segment-queued-jobs)))) -(defun my-segment-media-file-1 (media-file-name desc-file-name) +(defun my-ffmpeg-split-file (file-name split-at) + "Split FILE-NAME at SPLIT-AT into two files." + (let* ((name-no-ext (file-name-sans-extension file-name)) + (ext (file-name-extension file-name)) + (file-name-1 (make-temp-file (format "%s-1-" name-no-ext) nil + (format ".%s" ext))) + (file-name-2 (make-temp-file (format "%s-2-" name-no-ext) nil + (format ".%s" ext)))) + (message "Splitting %s at %s into %s and %s..." + file-name split-at file-name-1 file-name-2) + (set-process-sentinel + (start-process (format "ffmpeg-%s" file-name) + (format "*ffmpeg-%s*" file-name) + "ffmpeg" + "-i" file-name + "-to" split-at "-c" "copy" file-name-1 + "-ss" split-at "-c" "copy" file-name-2 + "-y") + (lambda (proc event) + (let ((status (process-exit-status proc))) + (if (eq status 0) + (progn + (message "Splitting %s at %s into %s and %s... Done" + file-name split-at file-name-1 file-name-2)) + (message "Splitting %s at %s into %s and %s... Failed: %s" + file-name split-at file-name-1 file-name-2 event))))))) + +(defun my-dired-do-ffmpeg-split-file () + (interactive) + (seq-do + (lambda (file) + (my-ffmpeg-split-file file (read-string + (format "Split %s at: " file)))) + (dired-get-marked-files))) + +(defun my-segment-media-file-2 (media-file-name info-file-name) + "Run ffmpeg to segment MEDIA-FILE-NAME according to INFO-FILE-NAME in one go. + +Much faster than my-segment-media-file or my-segment-media-file-1." + (interactive (list + (read-file-name "Choose media file: ") + (read-file-name + "Choose description file (.info.json or .description): "))) + (let* ((dir (file-name-sans-extension (expand-file-name media-file-name))) + (info (my-get-media-segments info-file-name)) + (total (length info)) + (pad (1+ (floor (log10 total)))) + (idx 0) + (args `("-i" ,(expand-file-name media-file-name)))) + (ignore-errors (dired-create-directory dir)) + (dolist (media info) + (setq idx (1+ idx)) + (let* ((title (plist-get media :title)) + (start (plist-get media :start)) + (end (plist-get media :end))) + (setq args (append args + `("-ss" ,start) + (when end `("-to" ,end)) + `("-c" "copy" + ,(format + (format "%%s/%%0%dd-%%s.%%s" pad) dir idx title + (file-name-extension media-file-name))))) + (message "Will cut %s-%s to %s (%d/%d)..." + start (or end "") title idx total))) + (set-process-sentinel + (apply 'start-process + (append `(,(format "ffmpeg-%s" media-file-name) + ,(format "*ffmpeg-%s*" media-file-name) + "ffmpeg") + args)) + (lambda (proc event) + (let ((status (process-exit-status proc))) + (if (eq status 0) + (progn + (message "Cutting %s: All DONE" media-file-name)) + (message "Cutting %s FAILED: %s" media-file-name event))))))) + +(defun my-segment-media-file-1 (media-file-name info-file-name) "Run ffmpeg asynchronously to segment file-name according to description. Uses `my-media-segment-max-inflight' to limit number of inflight tasks." (interactive (list (read-file-name "Choose media file: ") - (read-file-name "Choose description file: "))) + (read-file-name + "Choose description file (.info.json or .description): "))) (let* ((dir (file-name-sans-extension (expand-file-name media-file-name))) - (info (my-get-media-segments - (with-temp-buffer - (insert-file-contents desc-file-name) - (buffer-string)))) + (info (my-get-media-segments info-file-name)) (total (length info)) (pad (1+ (floor (log10 total)))) (idx 0) @@ -94,12 +169,31 @@ Uses `my-media-segment-max-inflight' to limit number of inflight tasks." (funcall thunk) (my-media-segment-enqueue-process thunk)))))) -(defun my-get-media-segments (description) +(defun my-get-media-segments (info-file-name) + (if (equal (file-name-extension info-file-name) "json") + (my-get-media-segments-from-json info-file-name) + (my-get-media-segments-from-descr info-file-name))) + +(defun my-get-media-segments-from-json (json-file-name) + (let ((info + (with-temp-buffer + (insert-file-contents json-file-name) + (goto-char (point-min)) + (json-read)))) + (seq-map + (lambda (ch) + (let-alist ch + ;; .title: ytdl; .tags.titile: .m4b + (list :title (my-make-doc-file-name (or .title .tags.title)) + :start (format "%s" .start_time) + :end (format "%s" .end_time)))) + (alist-get 'chapters info)))) + +(defun my-get-media-segments-from-descr (descr-file-name) "Output title start end triplets." (let ((results) (title) (start) (end)) (with-temp-buffer - (erase-buffer) - (insert description) + (insert-file-contents descr-file-name) (goto-char (point-min)) (save-excursion (while (re-search-forward @@ -116,7 +210,7 @@ Uses `my-media-segment-max-inflight' to limit number of inflight tasks." (buffer-substring-no-properties (point) (progn (beginning-of-line 2) (point)))))) - (push (list :title (my-make-filename title) :start start :end end) results) + (push (list :title (my-make-doc-file-name title) :start start :end end) results) ) (setq end nil) (dolist (result results) @@ -127,19 +221,16 @@ Uses `my-media-segment-max-inflight' to limit number of inflight tasks." ))) (defvar my-segment-media-max-async 10) -(defun my-segment-media-file (media-file-name desc-file-name synchronously) +(defun my-segment-media-file (media-file-name info-file-name synchronously) "Run ffmpeg asynchronously to segment file-name according to description. With a prefix-arg, run synchronously." (interactive (list (read-file-name "Choose media file: ") - (read-file-name "Choose description file: ") + (read-file-name "Choose info file: ") current-prefix-arg)) (let* ((dir (file-name-sans-extension (expand-file-name media-file-name))) - (info (my-get-media-segments - (with-temp-buffer - (insert-file-contents desc-file-name) - (buffer-string)))) + (info (my-get-media-segments info-file-name)) (total (length info)) (idx 0)) (when (or synchronously (<= total my-segment-media-max-async) |