#!usr/bin/env python
# -*- coding: utf-8 -*-
# author: kuangdd
# date: 2020/1/5
"""
### audio_cli
命令行，播放音频，去除背景噪声，音频格式转换。
支持递归处理文件夹内的全部音频。

#### 命令行

##### **说明**

- 用位置参数来控制。
- 名称说明
    * inpath：输入音频路径或目录。
    * outpath：输出音频路径或目录，如果为目录，则输出的子目录按照inpath的子目录格式输出。
    * sr：音频采样率，默认16000或自动识别采样率。
    * in_format：输入音频格式，主要用以限制为指定后缀名的文件，如果不设置，则处理目录的全部文件。
    * out_format：输出音频格式，主要用以音频格式转换，设置输出音频的后缀名。
- 中括号【[]】里面的是可选参数。

#### **工具**
- auplay: 播放音频

```
auplay inpath [sr] [in_format]
```

- aunoise: 语音降噪

```
aunoise inpath outpath [in_format]
```


- auformat: 音频格式转换

```
auformat inpath outpath out_format [in_format]
```



"""
from pathlib import Path
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(Path(__name__).stem)

import sys
from functools import partial
from multiprocessing import Pool
from tqdm import tqdm
from pathlib import Path
import traceback
import multiprocessing as mp

from .audio_io import _sr
from .audio_player import play_audio
from .audio_noise_remover import remove_noise_os
from .audio_editor import convert_format_os

_n_process = max(1, mp.cpu_count() - 2)


def play_audio_one(inpath, sr):
    try:
        play_audio(inpath, sr=sr)
    except Exception as e:
        logger.info("PlayAudioFailed: {}".format(inpath))
        traceback.print_exc()


def play_audio_cli():
    fpath = Path(sys.argv[1])
    if len(sys.argv) >= 3:
        sr = int(sys.argv[2])
    else:
        sr = _sr
    if len(sys.argv) >= 4:
        in_format = sys.argv[3]
    else:
        in_format = None

    if fpath.is_file():
        play_audio_one(fpath, sr=sr)
    elif fpath.is_dir():
        tmp = "**/*" if in_format is None else "**/*.{}".format(in_format)
        for inpath in sorted(fpath.glob(tmp)):
            if not inpath.is_file():
                continue
            try:
                play_audio_one(inpath, sr=sr)
            except Exception as e:
                logger.info("PlayAudioFailed: {}".format(inpath))
                traceback.print_exc()
    else:
        assert fpath.exists()


def remove_noise_one(x):
    outpath = Path(x["outpath"])
    outpath.parent.mkdir(exist_ok=True, parents=True)
    try:
        out = remove_noise_os(**x)
    except Exception as e:
        inpath = str(x["inpath"])
        logger.info("RemoveNoiseFailed: {}".format(inpath))
        traceback.print_exc()
        out = None
    return out


def remove_noise_cli():
    inpath = Path(sys.argv[1])
    outpath = Path(sys.argv[2])
    if len(sys.argv) >= 4:
        in_format = sys.argv[3]
    else:
        in_format = None

    if inpath.is_file():
        remove_noise_one(dict(inpath=inpath, outpath=outpath))
    elif inpath.is_dir():
        indir = inpath
        outdir = outpath
        kw_lst = []
        tmp = "**/*" if in_format is None else "**/*.{}".format(in_format)
        for inpath in indir.glob(tmp):
            if not inpath.is_file():
                continue
            parts = inpath.relative_to(indir).parts
            outpath = outdir.joinpath(*parts)
            kw = dict(inpath=str(inpath), outpath=str(outpath))
            kw_lst.append(kw)

        pool_jobs(func=remove_noise_one, n_process=_n_process, kwargs_list=kw_lst, tqdm_desc='remove_noise')
    else:
        assert inpath.exists()


def convert_format_one(x):
    outpath = Path(x["outpath"])
    outpath.parent.mkdir(exist_ok=True, parents=True)
    try:
        out = convert_format_os(**x)
    except Exception as e:
        inpath = str(x["inpath"])
        logger.info("ConvertFormatFailed: {}".format(inpath))
        traceback.print_exc()
        out = None
    return out


def convert_format_cli():
    inpath = Path(sys.argv[1])
    outpath = Path(sys.argv[2])
    out_format = sys.argv[3]
    if len(sys.argv) >= 5:
        in_format = sys.argv[4]
    else:
        in_format = None

    if inpath.is_file():
        convert_format_one(dict(inpath=inpath, outpath=outpath, in_format=in_format, out_format=out_format))
    elif inpath.is_dir():
        indir = inpath
        outdir = outpath
        kw_lst = []
        tmp = "**/*" if in_format is None else "**/*.{}".format(in_format)
        for inpath in indir.glob(tmp):
            if not inpath.is_file():
                continue
            parts = inpath.parent.relative_to(indir).parts
            name = "{}.{}".format(inpath.stem, out_format)
            outpath = outdir.joinpath(*parts, name)
            kw = dict(inpath=str(inpath), outpath=str(outpath), in_format=in_format, out_format=out_format)
            kw_lst.append(kw)

        pool_jobs(func=convert_format_one, n_process=_n_process, kwargs_list=kw_lst, tqdm_desc='convert_format')
    else:
        assert inpath.exists()


def pool_jobs(func, n_process=_n_process, kwargs_list=(), post_func=None, tqdm_desc="job"):
    """
    多进程执行任务。
    :param func: 第一个参数为变化参数，如果多个参数组合变化，请用：【def _func(x): return func(**x)】的方式处理函数。
    :param n_process:
    :param kwargs_list:
    :param post_func:
    :param tqdm_desc:
    :return:
    """
    if post_func is None:
        post_func = lambda x: x
    _tqdm = lambda x: tqdm(x, desc=tqdm_desc, total=len(kwargs_list), ncols=80, mininterval=1)
    if n_process == 0 or n_process == 1:
        for kw in _tqdm(kwargs_list):
            out = func(kw)
            post_func(out)
    else:
        partial_func = partial(func)
        job = Pool(n_process).imap(partial_func, kwargs_list)
        for out in list(_tqdm(job)):
            post_func(out)


if __name__ == "__main__":
    print(__file__)
