Skip to content

Commit

Permalink
desktop.media.audio-split-m4b: run ffmpeg subprocesses in parallel
Browse files Browse the repository at this point in the history
  • Loading branch information
mk-fg committed Apr 6, 2024
1 parent 874ee1b commit 25e103d
Showing 1 changed file with 33 additions and 15 deletions.
48 changes: 33 additions & 15 deletions desktop/media/audio-split-m4b
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python

import os, sys, re, math, json, subprocess as sp, datetime as dt
import os, sys, re, math, json, time, subprocess as sp, datetime as dt


err_fmt = lambda err: f'[{err.__class__.__name__}] {err}'
Expand Down Expand Up @@ -45,21 +45,27 @@ def title_subs_apply(title, _res=list()):


def main(args=None):
import argparse
import argparse, textwrap
dd = lambda text: re.sub( r' \t+', ' ',
textwrap.dedent(text).strip('\n') + '\n' ).replace('\t', ' ')
parser = argparse.ArgumentParser(
description='Split specified m4b audio file on chapters.'
' Does not do any transcoding, which can be done on resulting aac files afterwards.')
formatter_class=argparse.RawTextHelpFormatter, description=dd('''
Split specified m4b audio file on chapters.
Does not do any transcoding, which can be done on resulting aac files afterwards.'''))

parser.add_argument('path', help='Path to source m4b file.')

parser.add_argument('-n', '--name-format',
metavar='str.format', default='{n:03d}__{title}.aac',
help='Template for output filenames as python str.format template string.'
' Can contain following keys: n, id, title, title_raw, a, b. Default: %(default)s.')
parser.add_argument('-f', '--name-format',
metavar='str.format', default='{n:03d}__{title}.aac', help=dd('''
Template for output filenames as python str.format template string.'
Can contain following keys: n, id, title, title_raw, a, b. Default: %(default)s.'''))
parser.add_argument('--name-format-raw', action='store_true',
help='Avoid doing any string replacements on filename (to make it more fs-friendly).')

parser.add_argument('--dry-run', action='store_true',
parser.add_argument('-j', '--max-parallel', type=int, metavar='n', help=dd(f'''
Max number of processing jobs to run in parallel.
Default or 0 will be set to number of available cpu threads ({os.cpu_count()} here).'''))
parser.add_argument('-n', '--dry-run', action='store_true',
help='Do not slice the file, just print output filenames.')
parser.add_argument('-d', '--debug', action='store_true', help='Verbose operation mode.')
opts = parser.parse_args(sys.argv[1:] if args is None else args)
Expand All @@ -79,21 +85,33 @@ def main(args=None):
for c in map(adict, meta['chapters']) ), key=lambda c: c.id)
log.debug('Parsed %s chapters from: %s', len(meta), opts.path)

ts_fmt = '{:f}'
try:
if not all(int(c.title) == n for n, c in enumerate(meta, 1)): raise ValueError
log.info('Auto-labelling number-only chapters as "cXYZ"')
for c in meta: c.title = f'c{int(c.title):03,d}'
except: raise

procs, procs_max = dict(), opts.max_parallel or os.cpu_count()
def _procs_cycle(n=None, cmd=None, limit=procs_max):
while len(procs) >= limit:
for k, proc in list(procs.items()):
if proc.poll() is None: continue
if procs.pop(k).wait(): raise RuntimeError(f'ffmpeg failed for chapter #{k}')
time.sleep(0.1)
if n: procs[n] = sp.Popen(cmd)

ts_fmt = '{:f}'
for n, c in enumerate(meta, 1):
c.update(n=n, title_raw=c.title)
if not opts.name_format_raw: c.title = title_subs_apply(c.title)
dst_path = opts.name_format.format(**c)
log.info( 'Copying slice %s - %s [ start: %s, len: %s, title: %s ] to file: %s',
c.a, c.b, td_repr(c.a), td_repr(c.b - c.a), c.title_raw, dst_path )
if not opts.dry_run:
sp.run([ 'ffmpeg', '-loglevel', 'warning', '-y', '-i', opts.path, '-acodec', 'copy',
'-ss', ts_fmt.format(c.a), '-to', ts_fmt.format(c.b), dst_path ], check=True)
log.info(
'Copying chapter #%s %s - %s [ start: %s, len: %s, title: %s ] to file: %s',
n, c.a, c.b, td_repr(c.a), td_repr(c.b - c.a), c.title_raw, dst_path )
if opts.dry_run: continue
_procs_cycle(n, [ 'ffmpeg', '-loglevel', 'error', '-y', '-i', opts.path,
'-acodec', 'copy', '-ss', ts_fmt.format(c.a), '-to', ts_fmt.format(c.b), dst_path ])
_procs_cycle(limit=1)

log.debug('Finished')

Expand Down

0 comments on commit 25e103d

Please sign in to comment.