aboutsummaryrefslogtreecommitdiff
path: root/emacs/.emacs.d/lisp/my/my-media-segment.el
diff options
context:
space:
mode:
Diffstat (limited to 'emacs/.emacs.d/lisp/my/my-media-segment.el')
-rw-r--r--emacs/.emacs.d/lisp/my/my-media-segment.el123
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)