New Upstream Release - ffcvt

Ready changes

Summary

Merged new upstream version: 1.11.1 (was: 1.7.6).

Diff

diff --git a/.all-contributorsrc b/.all-contributorsrc
deleted file mode 100644
index 8309cb2..0000000
--- a/.all-contributorsrc
+++ /dev/null
@@ -1,80 +0,0 @@
-{
-  "files": [
-    "README.md",
-    "README.beg.e.md",
-    "README.end2.e.md"
-  ],
-  "imageSize": 100,
-  "commit": false,
-  "contributors": [
-    {
-      "login": "suntong",
-      "name": "suntong",
-      "avatar_url": "https://avatars.githubusercontent.com/u/422244?v=4",
-      "profile": "https://github.com/suntong",
-      "contributions": [
-        "code",
-        "ideas",
-        "design",
-        "data",
-        "test",
-        "bug",
-        "doc",
-        "blog",
-        "example",
-        "tutorial",
-        "tool",
-        "platform",
-        "review",
-        "question",
-        "maintenance",
-        "infra"
-      ]
-    },
-    {
-      "login": "sanjaymsh",
-      "name": "sanjaymsh",
-      "avatar_url": "https://avatars.githubusercontent.com/u/66668807?v=4",
-      "profile": "https://github.com/sanjaymsh",
-      "contributions": [
-        "platform"
-      ]
-    },
-    {
-      "login": "bjwest",
-      "name": "Billy West",
-      "avatar_url": "https://avatars.githubusercontent.com/u/1934590?v=4",
-      "profile": "https://github.com/bjwest",
-      "contributions": [
-        "bug",
-        "userTesting"
-      ]
-    },
-    {
-      "login": "bdaroz",
-      "name": "Brian Rozmierski",
-      "avatar_url": "https://avatars.githubusercontent.com/u/9668896?v=4",
-      "profile": "https://github.com/bdaroz",
-      "contributions": [
-        "bug",
-        "userTesting"
-      ]
-    },
-    {
-      "login": "brainstorm",
-      "name": "Roman Valls Guimera",
-      "avatar_url": "https://avatars.githubusercontent.com/u/175587?v=4",
-      "profile": "http://blogs.nopcode.org/brainstorm",
-      "contributions": [
-        "bug",
-        "userTesting"
-      ]
-    }
-  ],
-  "contributorsPerLine": 7,
-  "projectName": "ffcvt",
-  "projectOwner": "suntong",
-  "repoType": "github",
-  "repoHost": "https://github.com",
-  "skipCi": true
-}
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
deleted file mode 100644
index 56dff65..0000000
--- a/.github/workflows/codeql-analysis.yml
+++ /dev/null
@@ -1,76 +0,0 @@
-# For most projects, this workflow file will not need changing; you simply need
-# to commit it to your repository.
-#
-# You may wish to alter this file to override the set of languages analyzed,
-# or to provide custom queries or build logic.
-#
-# ******** NOTE ********
-# We have attempted to detect the languages in your repository. Please check
-# the `language` matrix defined below to confirm you have the correct set of
-# supported CodeQL languages.
-#
-name: "CodeQL"
-
-on:
-  push:
-    branches: [ main ]
-    paths-ignore:
-      - '**/*.md'
-  pull_request:
-    # The branches below must be a subset of the branches above
-    branches: [ main ]
-    paths-ignore:
-      - '**/*.md'
-  schedule:
-    # The default branch at 14:32 UTC on the 1st of every month
-    - cron: '32 14 1 * *'
-
-jobs:
-  analyze:
-    name: Analyze
-    runs-on: ubuntu-latest
-    permissions:
-      actions: read
-      contents: read
-      security-events: write
-
-    strategy:
-      fail-fast: false
-      matrix:
-        language: [ 'go' ]
-        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
-        # Learn more:
-        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
-
-    steps:
-    - name: Checkout repository
-      uses: actions/checkout@v2
-
-    # Initializes the CodeQL tools for scanning.
-    - name: Initialize CodeQL
-      uses: github/codeql-action/init@v1
-      with:
-        languages: ${{ matrix.language }}
-        # If you wish to specify custom queries, you can do so here or in a config file.
-        # By default, queries listed here will override any specified in a config file.
-        # Prefix the list here with "+" to use these queries and those in the config file.
-        # queries: ./path/to/local/query, your-org/your-repo/queries@main
-
-    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
-    # If this step fails, then you should remove it and run the build manually (see below)
-    - name: Autobuild
-      uses: github/codeql-action/autobuild@v1
-
-    # ℹ️ Command-line programs to run using the OS shell.
-    # πŸ“š https://git.io/JvXDl
-
-    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
-    #    and modify them (or add more) to build your code if your project
-    #    uses a compiled language
-
-    #- run: |
-    #   make bootstrap
-    #   make release
-
-    - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/go-release-build.yml b/.github/workflows/go-release-build.yml
deleted file mode 100644
index 3a86022..0000000
--- a/.github/workflows/go-release-build.yml
+++ /dev/null
@@ -1,84 +0,0 @@
-name: build
-
-on:
-  push:
-    branches:
-      - 'master'
-    tags:
-      - 'v*'
-
-jobs:
-  build:
-    runs-on: ubuntu-latest
-    steps:
-      -
-        name: Set up Go
-        uses: actions/setup-go@v2
-        with:
-          go-version: 1.15
-
-      -
-        name: Install Cloudsmith CLI
-        run: pip install --upgrade cloudsmith-cli
-        # Cloudsmith CLI tooling for pushing releases
-        # See https://help.cloudsmith.io/docs/cli
-
-      -
-        name: Checkout
-        uses: actions/checkout@v2
-        with:
-          fetch-depth: 0
-
-      -
-        name: Tests
-        run: |
-          export GOPATH=/home/runner/go
-          mkdir -p $GOPATH/src/github.com/$GITHUB_ACTOR
-          mv $GITHUB_WORKSPACE $GOPATH/src/github.com/$GITHUB_ACTOR
-          ln -s $GOPATH/src/github.com/$GITHUB_REPOSITORY $GITHUB_WORKSPACE
-          # go mod tidy
-          go get -v ./...
-          go test -v ./...
-          go build -v .
-          #pwd
-          #ls -Al test/* test2/*
-          ( cd test; ./test-all.sh; )
-          ( cd test2; ./test-all.sh; )
-
-      -
-        name: Run GoReleaser
-        uses: goreleaser/goreleaser-action@v2
-        if: success() && startsWith(github.ref, 'refs/tags/')
-        with:
-          version: latest
-          args: release --rm-dist
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-      # Publish to cloudsmith repo
-      -
-        name: Publish package to cloudsmith
-        if: success() && startsWith(github.ref, 'refs/tags/')
-        env:
-          CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
-        run: |
-          for filepath in dist/*; do
-            echo "== Analyzing '$filepath' for publishing"
-            filename=$(basename -- "$filepath")
-            extension="${filename##*.}"
-            filename="${filename%.*}"
-            case "$extension" in
-            'apk')
-              echo "Pushing '$filepath' to cloudsmith repo"
-              cloudsmith push alpine suntong/repo/alpine/any-version $filepath
-              ;;
-            'deb' | 'rpm')
-              echo "Pushing '$filepath' to cloudsmith repo"
-              cloudsmith push $extension suntong/repo/any-distro/any-version $filepath
-              ;;
-            *)
-              echo "File .$extension skipped publishing"
-              echo
-              ;;
-            esac
-          done
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 9dd960d..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,27 +0,0 @@
-.all-contributorsrc
-# Compiled Object files, Static and Dynamic libs (Shared Objects)
-*.o
-*.a
-*.so
-
-# Folders
-_obj
-_test
-
-# Architecture specific extensions/prefixes
-*.[568vq]
-[568vq].out
-
-*.cgo1.go
-*.cgo2.c
-_cgo_defun.c
-_cgo_gotypes.go
-_cgo_export.*
-
-_testmain.go
-
-*.exe
-*.test
-*.prof
-ffcvt
-*~
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
deleted file mode 100644
index 725b555..0000000
--- a/.gitlab-ci.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-image: golang:1.10
-
-stages:
- - build
- - test
-
-before_script:
- # Create a symbolic link under $GOPATH, this is needed for local build
- - cd $GOPATH/src
- - mkdir -p gitlab.com/$CI_PROJECT_NAMESPACE
- - cd gitlab.com/$CI_PROJECT_NAMESPACE
- - ln -s $CI_PROJECT_DIR
- - cd $CI_PROJECT_NAME
- - go get ./...
-
-build-test:
- script:
-   # == build
-   # - godep restore
-   # - godep go build
-   - go build -v -ldflags="-X main.date=`date -I$TIMESPEC`"; date -I$TIMESPEC
-
-   # == test
-   - $CI_PROJECT_NAME
-   - $CI_PROJECT_NAME -version
-   # - godep restore
-   # - godep go test -v -cover ./...
-   - go test -v -cover ./...
-   - ( pwd; cd test; pwd; ls -l; ./test-all.sh; )
diff --git a/.goreleaser.yml b/.goreleaser.yml
deleted file mode 100644
index bc1998f..0000000
--- a/.goreleaser.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-project_name: ffcvt
-
-archives:
-  - format: tar.gz
-    wrap_in_directory: true
-    format_overrides:
-      - goos: windows
-        format: zip
-    # remove README and LICENSE
-    files:
-      - none*
-
-builds:
-  - env: [CGO_ENABLED=0]
-    goos:
-      - linux
-      - windows
-      - darwin
-    goarch:
-      - amd64
-      - arm64
-
-nfpms:
-- maintainer: Tong Sun <suntong@cpan.org>
-  description: ffmpeg convert wrapper tool
-  homepage: https://github.com/suntong/ffcvt
-  license: MIT
-  formats:
-  - deb
-  - rpm
-  - apk
diff --git a/README.beg.e.md b/README.beg.e.md
deleted file mode 100644
index eb08897..0000000
--- a/README.beg.e.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# {{.Name}}
-<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
-[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-)
-<!-- ALL-CONTRIBUTORS-BADGE:END -->
-
-{{render "license/shields" . "License" "MIT"}}
-{{template "badge/godoc" .}}
-{{template "badge/goreport" .}}
-[![Build Status](https://github.com/{{.User}}/{{.Name}}/actions/workflows/go-release-build.yml/badge.svg?branch=master)](https://github.com/{{.User}}/{{.Name}}/actions/workflows/go-release-build.yml)
-[![PoweredBy WireFrame](https://github.com/go-easygen/wireframe/blob/master/PoweredBy-WireFrame-B.svg)](http://godoc.org/github.com/go-easygen/wireframe)
-
-{{pkgdoc}}
-
diff --git a/README.e.md b/README.e.md
deleted file mode 100644
index 48b47d2..0000000
--- a/README.e.md
+++ /dev/null
@@ -1,96 +0,0 @@
-## {{toc 5}}
-- [Install Debian/Ubuntu package](#install-debianubuntu-package)
-- [Download/install binaries](#downloadinstall-binaries)
-  - [The binary executables](#the-binary-executables)
-  - [Distro package](#distro-package)
-  - [Debian package](#debian-package)
-- [Install Source](#install-source)
-- [Author](#author)
-- [Contributors](#contributors-)
-
-## {{.Name}} - ffmpeg convert wrapper tool
-
-### Latest Update(s)
-
-#### Release v1.7.5
-
-* Now able to speed up playback speed (`-Speed`). Details in [\#22](https://github.com/suntong/ffcvt/issues/22)
-* Also have added a `copy` target type that can speed up the `Seg` (split video) operation (v1.7.4). Details in [\#21](https://github.com/suntong/ffcvt/issues/21)
-
-#### Release v1.7.3
-
-* Now able to split video into multiple segments (`-S,Seg`) by the given time. Details in [\#16](https://github.com/suntong/ffcvt/issues/16)
-
-#### Release v1.7.2
-
-* Able to [choose streams by language, instead of streams index. ](https://github.com/suntong/ffcvt/commit/f649609356ef06d22d17d6dbe3f89b945cf18643)Details in [\#9](https://github.com/suntong/ffcvt/issues/9)
-* Fixed [\#8](https://github.com/suntong/ffcvt/issues/8). Now [force copy all subtitle streams. ](https://github.com/suntong/ffcvt/commit/46ce6725f9b036d373c6836d3bd66b429d5c4b2f)Details in [\#8](https://github.com/suntong/ffcvt/issues/8)
-* [Added option -sel](https://github.com/suntong/ffcvt/commit/defc5df5168216e279b944590f1d92523ecadc60), so now able to pick subtitle language(s). Details in [\#12](https://github.com/suntong/ffcvt/issues/12)
-
-#### Release v1.7.1
-
-Added option `-C,Cut` which allows cutting multiple segments.
-
-For further details, check out the wiki https://git.io/JuK0c,
-in which the source file of
-
-https://user-images.githubusercontent.com/422244/132961501-a2344db0-c48c-4a57-90fa-c3746bf3025f.mp4
-
-is cut-short into
-
-https://user-images.githubusercontent.com/422244/132961530-ea65cd03-19f8-4e7c-a871-40218f7289cc.mp4
-
-
-#### Release v1.7.0
-
-Added `wx` type for weixin.
-
-Convert to video that is recognizable and playable by weixin/wechat, by using the `-t wx` option as the convertion type. Here is a converted sample:
-
-https://user-images.githubusercontent.com/422244/132617136-e1371ef3-6a21-4f12-8324-6db003c12468.mp4
-
-(credit [here](https://www.youtube.com/watch?v=2-UzBitLmf8))
-
-For further details, check out the wiki https://git.io/JuK0q
-
-## Introduction
-
-- The next-generation codec like [High Efficiency Video codec (HEVC), H.265](https://goo.gl/IZrDH2) or [VP9](https://developers.google.com/media/vp9/) can produce videos visually comparable to H.264's result, but in [about half the file size](https://trac.ffmpeg.org/wiki/Encode/H.265).
-- Meanwhile the [Opus](https://goo.gl/BPUkTf) [audio codec](https://goo.gl/IZrDH2) is becoming the best thing ever for compressing audio -- A 64K Opus audio stream is comparable to mp3 files of 128K to 256K bandwidth.
-- Such fantastic high efficiency audio/video codec/encoding capability has long been available in `ffmpeg`, but fewer people know it or use it, partly because the `ffmpeg` command line is not that simple for every one.
-- The `ffcvt` is designed to take the burden from normal Joe -- All you need to do to encode a video is to give one parameter to `ffcvt`, i.e., the path and file name of the video to be encoded, and `ffcvt` will take care of the rest, using the recommended values for both audio/video encoding to properly encode it for you.
-- It can't be more simpler than that. However, beneath the simple surface, `ffcvt` is versatile and powerful enough to allow you to touch every corner of audio/video encoding. There is a huge list of environment variables (or command-line parameters) which will allow you tweak the encoding methods and parameters to exactly what you prefer instead.
-- Moreover, to encode a directory full of video files, including under its sub-directories, you need just to give `ffcvt` one single parameter, the directory location, and `ffcvt` will go ahead and encode all video files under that directory, including all its sub-directories as well. 
-
-## Quick Usage
-
-There is a quick usage help that comes with `ffcvt`, produced when it is invoked without any parameters:
-
-### $ {{exec "ffcvt" | color "sh"}}
-
-
-## Environment Variables
-
-For each `ffcvt` command line parameter, there is a environment variable corresponding to it. For example you can use `export FFCVT_FFMPEG=avconv` to use `avconv` instead of `ffmpeg` (Don't, I use it for my [CommandLineArgs](https://github.com/suntong001/lang/blob/master/lang/Go/src/sys/CommandLineArgs.go) to develop/test `ffcvt` without invoking `ffmpeg` each time). 
-
-## Encoding Help
-
-The detailed guide to choose/provide proper parameters to `ffcvt` have been moved to [wiki](https://github.com/suntong/ffcvt/wiki/). For example,
-
-- [HEVC vs VP9](https://github.com/suntong/ffcvt/wiki/KB:-WebM-(VP9)-Encoding#hevc-vs-vp9)
-- [HEVC Preset Method Comparison](https://github.com/suntong/ffcvt/wiki/KB:-HEVC-(x265)-Encoding#preset-method-comparison)
-- [The HEVC CRF Comparison](https://github.com/suntong/ffcvt/wiki/KB:-HEVC-(x265)-Encoding#the-crf-comparison)
-- [Example 1: YouTube Encoding](https://github.com/suntong/ffcvt/wiki/Example:-YouTube-Encoding)
-- [Example 2: Talk Encoding](https://github.com/suntong/ffcvt/wiki/Example:-Talk-Encoding)
-
-Please check them out in the [wiki](https://github.com/suntong/ffcvt/wiki/), and for other documents like "Most used ffmpeg options", "How to crop a video", etc.
-
-## Tools Choices
-
-As suggested before, don't use `avconv`, use `ffmpeg` instead (the `avconv` fork was more for political reasons. I personally believe `ffmpeg` is technically superior although might not be politically).
-
-As for video/movie play back, use [mpv](http://mpv.io/). It is a fork of mplayer2 and MPlayer, and is a true *modern* *all-in-one* movie player that can play ANYTHING, and one of the few movie players being actively developed all the time. Download link is in [mpv.io](http://mpv.io/), from which Ubuntu repo I get my Ubuntu `ffmpeg` package as well. If you are unsatisfied with mpv's simple user interface, check out https://wiki.archlinux.org/index.php/Mpv#Front_ends.
-
-### Install Debian/Ubuntu package
-
-    apt install {{.Name}}
diff --git a/README.end.e.md b/README.end.e.md
deleted file mode 100644
index 1f34563..0000000
--- a/README.end.e.md
+++ /dev/null
@@ -1,71 +0,0 @@
-
-## Download/install binaries
-
-- The latest binary executables are available 
-as the result of the Continuous-Integration (CI) process.
-- I.e., they are built automatically right from the source code at every git release by [GitHub Actions](https://docs.github.com/en/actions).
-- There are two ways to get/install such binary executables
-  * Using the **binary executables** directly, or
-  * Using **packages** for your distro
-
-### The binary executables
-
-- The latest binary executables are directly available under  
-https://github.com/{{.User}}/{{.Name}}/releases/latest 
-- Pick & choose the one that suits your OS and its architecture. E.g., for Linux, it would be the `{{.Name}}_verxx_linux_amd64.tar.gz` file. 
-- Available OS for binary executables are
-  * Linux
-  * Mac OS (darwin)
-  * Windows
-- If your OS and its architecture is not available in the download list, please let me know and I'll add it.
-- The manual installation is just to unpack it and move/copy the binary executable to somewhere in `PATH`. For example,
-
-``` sh
-tar -xvf {{.Name}}_*_linux_amd64.tar.gz
-sudo mv -v {{.Name}}_*_linux_amd64/{{.Name}} /usr/local/bin/
-rmdir -v {{.Name}}_*_linux_amd64
-```
-
-
-### Distro package
-
-- [Packages available for Linux distros](https://cloudsmith.io/~suntong/repos/repo/packages/) are
-  * [Alpine Linux](https://cloudsmith.io/~suntong/repos/repo/setup/#formats-alpine)
-  * [Debian](https://cloudsmith.io/~suntong/repos/repo/setup/#formats-deb)
-  * [RedHat](https://cloudsmith.io/~suntong/repos/repo/setup/#formats-rpm)
-
-The repo setup instruction url has been given above.
-For example, for [Debian](https://cloudsmith.io/~suntong/repos/repo/setup/#formats-deb) --
-
-### Debian package
-
-
-```sh
-curl -1sLf \
-  'https://dl.cloudsmith.io/public/suntong/repo/setup.deb.sh' \
-  | sudo -E bash
-
-# That's it. You then can do your normal operations, like
-
-sudo apt-get update
-apt-cache policy {{.Name}}
-
-sudo apt-get install -y {{.Name}}
-```
-
-## Install Source
-
-To install the source code instead:
-
-```
-go get -v -u {{.ProjectURL}}
-```
-
-## Author
-
-Tong SUN  
-![suntong from cpan.org](https://img.shields.io/badge/suntong-%40cpan.org-lightgrey.svg "suntong from cpan.org")
-
-_Powered by_ [**WireFrame**](https://github.com/go-easygen/wireframe)  
-[![PoweredBy WireFrame](https://github.com/go-easygen/wireframe/blob/master/PoweredBy-WireFrame-Y.svg)](http://godoc.org/github.com/go-easygen/wireframe)  
-the _one-stop wire-framing solution_ for Go cli based projects, from _init_ to _deploy_.
diff --git a/README.end2.e.md b/README.end2.e.md
deleted file mode 100644
index 968e4f9..0000000
--- a/README.end2.e.md
+++ /dev/null
@@ -1,24 +0,0 @@
-
-## Contributors ✨
-
-Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
-
-<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
-<!-- prettier-ignore-start -->
-<!-- markdownlint-disable -->
-<table>
-  <tr>
-    <td align="center"><a href="https://github.com/suntong"><img src="https://avatars.githubusercontent.com/u/422244?v=4?s=100" width="100px;" alt=""/><br /><sub><b>suntong</b></sub></a><br /><a href="https://github.com/suntong/ffcvt/commits?author=suntong" title="Code">πŸ’»</a> <a href="#ideas-suntong" title="Ideas, Planning, & Feedback">πŸ€”</a> <a href="#design-suntong" title="Design">🎨</a> <a href="#data-suntong" title="Data">πŸ”£</a> <a href="https://github.com/suntong/ffcvt/commits?author=suntong" title="Tests">⚠️</a> <a href="https://github.com/suntong/ffcvt/issues?q=author%3Asuntong" title="Bug reports">πŸ›</a> <a href="https://github.com/suntong/ffcvt/commits?author=suntong" title="Documentation">πŸ“–</a> <a href="#blog-suntong" title="Blogposts">πŸ“</a> <a href="#example-suntong" title="Examples">πŸ’‘</a> <a href="#tutorial-suntong" title="Tutorials">βœ…</a> <a href="#tool-suntong" title="Tools">πŸ”§</a> <a href="#platform-suntong" title="Packaging/porting to new platform">πŸ“¦</a> <a href="https://github.com/suntong/ffcvt/pulls?q=is%3Apr+reviewed-by%3Asuntong" title="Reviewed Pull Requests">πŸ‘€</a> <a href="#question-suntong" title="Answering Questions">πŸ’¬</a> <a href="#maintenance-suntong" title="Maintenance">🚧</a> <a href="#infra-suntong" title="Infrastructure (Hosting, Build-Tools, etc)">πŸš‡</a></td>
-    <td align="center"><a href="https://github.com/sanjaymsh"><img src="https://avatars.githubusercontent.com/u/66668807?v=4?s=100" width="100px;" alt=""/><br /><sub><b>sanjaymsh</b></sub></a><br /><a href="#platform-sanjaymsh" title="Packaging/porting to new platform">πŸ“¦</a></td>
-    <td align="center"><a href="https://github.com/bjwest"><img src="https://avatars.githubusercontent.com/u/1934590?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Billy West</b></sub></a><br /><a href="https://github.com/suntong/ffcvt/issues?q=author%3Abjwest" title="Bug reports">πŸ›</a> <a href="#userTesting-bjwest" title="User Testing">πŸ““</a></td>
-    <td align="center"><a href="https://github.com/bdaroz"><img src="https://avatars.githubusercontent.com/u/9668896?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brian Rozmierski</b></sub></a><br /><a href="https://github.com/suntong/ffcvt/issues?q=author%3Abdaroz" title="Bug reports">πŸ›</a> <a href="#userTesting-bdaroz" title="User Testing">πŸ““</a></td>
-    <td align="center"><a href="http://blogs.nopcode.org/brainstorm"><img src="https://avatars.githubusercontent.com/u/175587?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Roman Valls Guimera</b></sub></a><br /><a href="https://github.com/suntong/ffcvt/issues?q=author%3Abrainstorm" title="Bug reports">πŸ›</a> <a href="#userTesting-brainstorm" title="User Testing">πŸ““</a></td>
-  </tr>
-</table>
-
-<!-- markdownlint-restore -->
-<!-- prettier-ignore-end -->
-
-<!-- ALL-CONTRIBUTORS-LIST:END -->
-
-This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
diff --git a/README.md b/README.md
index 2bf8d9a..eb71d11 100644
--- a/README.md
+++ b/README.md
@@ -10,9 +10,15 @@
 [![PoweredBy WireFrame](https://github.com/go-easygen/wireframe/blob/master/PoweredBy-WireFrame-B.svg)](http://godoc.org/github.com/go-easygen/wireframe)
 
 
+Transcodes all videos in the given directory and all of it's subdirectories
+using ffmpeg.
+
 ## TOC
 - [ffcvt - ffmpeg convert wrapper tool](#ffcvt---ffmpeg-convert-wrapper-tool)
   - [Latest Update(s)](#latest-update(s))
+    - [Release v1.10.0](#release-v1100)
+    - [Release v1.9.0](#release-v190)
+    - [Release v1.8.0](#release-v180)
     - [Release v1.7.5](#release-v175)
     - [Release v1.7.3](#release-v173)
     - [Release v1.7.2](#release-v172)
@@ -20,11 +26,11 @@
     - [Release v1.7.0](#release-v170)
   - [Introduction](#introduction)
   - [Quick Usage](#quick-usage)
+    - [$ ffcvt -version](#-ffcvt--version)
     - [$ ffcvt](#-ffcvt)
   - [Environment Variables](#environment-variables)
   - [Encoding Help](#encoding-help)
   - [Tools Choices](#tools-choices)
-    - [Install Debian/Ubuntu package](#install-debianubuntu-package)
 - [Install Debian/Ubuntu package](#install-debianubuntu-package)
 - [Download/install binaries](#downloadinstall-binaries)
   - [The binary executables](#the-binary-executables)
@@ -38,6 +44,29 @@
 
 ### Latest Update(s)
 
+#### Release v1.10.0
+
+Now able to
+
+* change the key signature of a song. Details in [\#30](https://github.com/suntong/ffcvt/issues/30).
+* add a karaoke audio track to the MTV videos. Details in [\#31](https://github.com/suntong/ffcvt/issues/31).
+
+
+#### Release v1.9.0
+
+- Release v1.9.0
+  * `ffcvt -version` now checks/outputs dependent program versions too
+  * now finished percentage are calculated from file size
+- Release v1.8.1, enable parallel execution
+
+#### Release v1.8.0
+
+* Now able to define your own defaults. Just make a copy of [ffcvt.json](ffcvt.json) and customize it to your heart's content, then use the `-cfg` option to point to it. Better yet, set `FFCVT_CFG` environment variable and forget all about it afterwards.
+  * This means that `ffcvt` is now not only limited to its own predefined transcoding sets, but you can also define your own transcoding rules and names and then fully enjoy its advanced addon assistants.
+  * BTW, If you have a good set, don't forget to send in a PR so that everybody can also benefit from it.
+* Now the subtitles, nfo, html or any files in the source directory will be duplicated into the output (work) directory, first by hard-link and if it fails due to cross storage devices, a copy will be used instead.
+* And when creating `par2` checksum/repair files, all files in the output (work) directory will be covered.
+
 #### Release v1.7.5
 
 * Now able to speed up playback speed (`-Speed`). Details in [\#22](https://github.com/suntong/ffcvt/issues/22)
@@ -92,6 +121,19 @@ For further details, check out the wiki https://git.io/JuK0q
 
 There is a quick usage help that comes with `ffcvt`, produced when it is invoked without any parameters:
 
+### $ ffcvt -version
+
+```sh
+$ ffcvt -version
+ffcvt version 1.10.0 built on 2023-05-18
+
+ffmpeg version 4.3.6-0+deb11u1 Copyright (c) 2000-2023 the FFmpeg developers
+built with gcc 10 (Debian 10.2.1-6)
+
+ffprobe version 4.3.6-0+deb11u1 Copyright (c) 2007-2023 the FFmpeg developers
+built with gcc 10 (Debian 10.2.1-6)
+```
+
 ### $ ffcvt
 ```sh
 Usage:
@@ -99,7 +141,8 @@ Usage:
 
 Flags:
 
-  -t	target type: webm/x265-opus/x264-mp3/wx/youtube/copy (FFCVT_T)
+  -cfg	cfg file to define your own targets: webm/wx/youtube etc (FFCVT_CFG)
+  -t	target type: webm/x265-opus/x264-mp3/wx/youtube/copy, or empty (FFCVT_T)
   -ves	video encoding method set (FFCVT_VES)
   -aes	audio encoding method set (FFCVT_AES)
   -ses	subtitle encoding method set (FFCVT_SES)
@@ -129,6 +172,9 @@ Flags:
 	strictly in the format of hh:mm:ss, and may repeat (FFCVT_C,CUT)
   -S,Seg	Split video into multiple segments (strictly in format: hh:mm:ss) (FFCVT_S,SEG)
   -Speed	Speed up/down video playback speed (e.g. 1.28) (FFCVT_SPEED)
+  -K,karaoke	Add a karaoke audio track to .mp4 MTV (FFCVT_K,KARAOKE)
+  -tkf	Transpose song's key from (e.g. C/C#/Db/D etc) (FFCVT_TKF)
+  -tkt	Transpose song's key to (e.g. -tkf C -tkt Db) (FFCVT_TKT)
   -lang	language selection for audio stream extraction (FFCVT_LANG)
   -sel	subtitle encoding language (language picked for reencoded video) (FFCVT_SEL)
   -o	more options that will pass to ffmpeg program (FFCVT_O)
@@ -153,6 +199,7 @@ Details:
   -Cut value
     	Cut segment(s) out to keep. Specify in the form of start-[end],
     		strictly in the format of hh:mm:ss, and may repeat
+  -K	Add a karaoke audio track to .mp4 MTV
   -S string
     	Split video into multiple segments (strictly in format: hh:mm:ss)
   -Seg string
@@ -173,6 +220,8 @@ Details:
     	no audio, output video only
   -ato-opus
     	audio encode to opus, using -abr
+  -cfg string
+    	cfg file to define your own targets: webm/wx/youtube etc
   -crf string
     	the CRF value: 0-51. Higher CRF gives lower quality
     		 (28 for x265, ~ 23 for x264)
@@ -192,6 +241,8 @@ Details:
     	ffprobe program execution (default "ffprobe -print_format flat")
   -force
     	overwrite any existing none-empty file
+  -karaoke
+    	Add a karaoke audio track to .mp4 MTV
   -lang string
     	language selection for audio stream extraction (default "eng")
   -n	no exec, dry run
@@ -211,7 +262,11 @@ Details:
   -sym
     	symlinks will be processed as well
   -t string
-    	target type: webm/x265-opus/x264-mp3/wx/youtube/copy (default "webm")
+    	target type: webm/x265-opus/x264-mp3/wx/youtube/copy, or empty (default "webm")
+  -tkf string
+    	Transpose song's key from (e.g. C/C#/Db/D etc)
+  -tkt string
+    	Transpose song's key to (e.g. -tkf C -tkt Db)
   -vc
     	copy video codec
   -vea string
@@ -257,9 +312,9 @@ As suggested before, don't use `avconv`, use `ffmpeg` instead (the `avconv` fork
 
 As for video/movie play back, use [mpv](http://mpv.io/). It is a fork of mplayer2 and MPlayer, and is a true *modern* *all-in-one* movie player that can play ANYTHING, and one of the few movie players being actively developed all the time. Download link is in [mpv.io](http://mpv.io/), from which Ubuntu repo I get my Ubuntu `ffmpeg` package as well. If you are unsatisfied with mpv's simple user interface, check out https://wiki.archlinux.org/index.php/Mpv#Front_ends.
 
-### Install Debian/Ubuntu package
+## Install Debian/Ubuntu package
 
-    apt install ffcvt
+    sudo apt install -y ffcvt
 
 ## Download/install binaries
 
@@ -309,10 +364,10 @@ curl -1sLf \
 
 # That's it. You then can do your normal operations, like
 
-sudo apt-get update
+sudo apt update
 apt-cache policy ffcvt
 
-sudo apt-get install -y ffcvt
+sudo apt install -y ffcvt
 ```
 
 ## Install Source
@@ -320,7 +375,7 @@ sudo apt-get install -y ffcvt
 To install the source code instead:
 
 ```
-go get -v -u github.com/suntong/ffcvt
+go install github.com/suntong/ffcvt@latest
 ```
 
 ## Author
diff --git a/VP9-CRF.png b/VP9-CRF.png
deleted file mode 100644
index 6ac3593..0000000
Binary files a/VP9-CRF.png and /dev/null differ
diff --git a/VP9-Speed.png b/VP9-Speed.png
deleted file mode 100644
index 319d3f6..0000000
Binary files a/VP9-Speed.png and /dev/null differ
diff --git a/config.go b/config.go
index f154631..9e4b976 100644
--- a/config.go
+++ b/config.go
@@ -8,6 +8,8 @@ import (
 	"flag"
 	"fmt"
 	"os"
+	"strconv"
+	"time"
 )
 
 ////////////////////////////////////////////////////////////////////////////
@@ -17,35 +19,41 @@ const progname = "ffcvt" // os.Args[0]
 
 // The Options struct defines the structure to hold the commandline values
 type Options struct {
-	Target     string // target type: webm/x265-opus/x264-mp3/wx/youtube/copy
-	Encoding          // anonymous field to hold encoding values
-	Directory  string // directory that hold input files
-	File       string // input file name (either -d or -f must be specified)
-	Links      bool   // symlinks will be processed as well
-	Exts       string // extension list for all the files to be queued
-	Suffix     string // suffix to the output file names
-	WDirectory string // work directory that hold output files
-	AC         bool   // copy audio codec
-	VC         bool   // copy video codec
-	AN         bool   // no audio, output video only
-	VN         bool   // no video, output audio only
-	VSS        bool   // video: same size
-	Cut        mFlags // Cut segment(s) out to keep. Specify in the form of start-[end],\n\tstrictly in the format of hh:mm:ss, and may repeat
-	Seg        string // Split video into multiple segments (strictly in format: hh:mm:ss)
-	Speed      string // Speed up/down video playback speed (e.g. 1.28)
-	Lang       string // language selection for audio stream extraction
-	SEL        mFlags // subtitle encoding language (language picked for reencoded video)
-	OptExtra   string // more options that will pass to ffmpeg program
-	A2Opus     bool   // audio encode to opus, using -abr
-	V2X265     bool   // video video encode to x265, using -crf
-	Par2C      bool   // par2create, create par2 files (in work directory)
-	NoClobber  bool   // no clobber, do not queue those already been converted
-	NoExec     bool   // no exec, dry run
-	Force      bool   // overwrite any existing none-empty file
-	Debug      int    // debugging level
-	FFMpeg     string // ffmpeg program executable name
-	FFProbe    string // ffprobe program execution
-	PrintV     bool   // print version then exit
+	Cfg        string        // cfg file to define your own targets: webm/wx/youtube etc
+	Target     string        // target type: webm/x265-opus/x264-mp3/wx/youtube/copy, or empty
+	Encoding                 // anonymous field to hold encoding values
+	Directory  string        // directory that hold input files
+	File       string        // input file name (either -d or -f must be specified)
+	Links      bool          // symlinks will be processed as well
+	Exts       string        // extension list for all the files to be queued
+	Suffix     string        // suffix to the output file names
+	WDirectory string        // work directory that hold output files
+	AC         bool          // copy audio codec
+	VC         bool          // copy video codec
+	AN         bool          // no audio, output video only
+	VN         bool          // no video, output audio only
+	VSS        bool          // video: same size
+	Cut        mFlags        // Cut segment(s) out to keep. Specify in the form of start-[end],\n\tstrictly in the format of hh:mm:ss, and may repeat
+	Seg        string        // Split video into multiple segments (strictly in format: hh:mm:ss)
+	Speed      string        // Speed up/down video playback speed (e.g. 1.28)
+	Karaoke    bool          // Add a karaoke audio track to .mp4 MTV
+	TranspFrom string        // Transpose song's key from (e.g. C/C#/Db/D etc)
+	TranspTo   string        // Transpose song's key to (e.g. -tkf C -tkt Db)
+	Lang       string        // language selection for audio stream extraction
+	SEL        mFlags        // subtitle encoding language (language picked for reencoded video)
+	OptExtra   string        // more options that will pass to ffmpeg program
+	A2Opus     bool          // audio encode to opus, using -abr
+	V2X265     bool          // video video encode to x265, using -crf
+	Par2C      bool          // par2create, create par2 files (in work directory)
+	NoClobber  bool          // no clobber, do not queue those already been converted
+	BreathTime time.Duration // breath time, interval between conversion to take a breath
+	MaxC       int           // max conversion done each run (default no limit)
+	NoExec     bool          // no exec, dry run
+	Force      bool          // overwrite any existing none-empty file
+	Debug      int           // debugging level
+	FFMpeg     string        // ffmpeg program executable name
+	FFProbe    string        // ffprobe program execution
+	PrintV     bool          // print version then exit
 }
 
 ////////////////////////////////////////////////////////////////////////////
@@ -60,8 +68,10 @@ var Opts Options
 func initVars() {
 
 	// set default values for command line parameters
+	flag.StringVar(&Opts.Cfg, "cfg", "",
+		"cfg file to define your own targets: webm/wx/youtube etc")
 	flag.StringVar(&Opts.Target, "t", "webm",
-		"target type: webm/x265-opus/x264-mp3/wx/youtube/copy")
+		"target type: webm/x265-opus/x264-mp3/wx/youtube/copy, or empty")
 	flag.StringVar(&Opts.VES, "ves", "",
 		"video encoding method set")
 	flag.StringVar(&Opts.AES, "aes", "",
@@ -118,6 +128,14 @@ func initVars() {
 		"Split video into multiple segments (strictly in format: hh:mm:ss)")
 	flag.StringVar(&Opts.Speed, "Speed", "",
 		"Speed up/down video playback speed (e.g. 1.28)")
+	flag.BoolVar(&Opts.Karaoke, "K", false,
+		"Add a karaoke audio track to .mp4 MTV")
+	flag.BoolVar(&Opts.Karaoke, "karaoke", false,
+		"Add a karaoke audio track to .mp4 MTV")
+	flag.StringVar(&Opts.TranspFrom, "tkf", "",
+		"Transpose song's key from (e.g. C/C#/Db/D etc)")
+	flag.StringVar(&Opts.TranspTo, "tkt", "",
+		"Transpose song's key to (e.g. -tkf C -tkt Db)")
 	flag.StringVar(&Opts.Lang, "lang", "eng",
 		"language selection for audio stream extraction")
 	flag.Var(&Opts.SEL, "sel",
@@ -133,6 +151,10 @@ func initVars() {
 		"par2create, create par2 files (in work directory)")
 	flag.BoolVar(&Opts.NoClobber, "nc", false,
 		"no clobber, do not queue those already been converted")
+	flag.DurationVar(&Opts.BreathTime, "bt", 120*time.Second,
+		"breath time, interval between conversion to take a breath")
+	flag.IntVar(&Opts.MaxC, "maxc", 0,
+		"max conversion done each run (default no limit)")
 	flag.BoolVar(&Opts.NoExec, "n", false,
 		"no exec, dry run")
 
@@ -151,6 +173,10 @@ func initVars() {
 func initVals() {
 	exists := false
 	// Now override those default values from environment variables
+	if len(Opts.Cfg) == 0 ||
+		len(os.Getenv("FFCVT_CFG")) != 0 {
+		Opts.Cfg = os.Getenv("FFCVT_CFG")
+	}
 	if len(Opts.Target) == 0 ||
 		len(os.Getenv("FFCVT_T")) != 0 {
 		Opts.Target = os.Getenv("FFCVT_T")
@@ -251,6 +277,17 @@ func initVals() {
 		len(os.Getenv("FFCVT_SPEED")) != 0 {
 		Opts.Speed = os.Getenv("FFCVT_SPEED")
 	}
+	if _, exists = os.LookupEnv("FFCVT_K,KARAOKE"); Opts.Karaoke || exists {
+		Opts.Karaoke = true
+	}
+	if len(Opts.TranspFrom) == 0 ||
+		len(os.Getenv("FFCVT_TKF")) != 0 {
+		Opts.TranspFrom = os.Getenv("FFCVT_TKF")
+	}
+	if len(Opts.TranspTo) == 0 ||
+		len(os.Getenv("FFCVT_TKT")) != 0 {
+		Opts.TranspTo = os.Getenv("FFCVT_TKT")
+	}
 	if len(Opts.Lang) == 0 ||
 		len(os.Getenv("FFCVT_LANG")) != 0 {
 		Opts.Lang = os.Getenv("FFCVT_LANG")
@@ -272,6 +309,12 @@ func initVals() {
 	if _, exists = os.LookupEnv("FFCVT_NC"); Opts.NoClobber || exists {
 		Opts.NoClobber = true
 	}
+	if Opts.MaxC == 0 ||
+		len(os.Getenv("FFCVT_MAXC")) != 0 {
+		if i, err := strconv.Atoi(os.Getenv("FFCVT_MAXC")); err == nil {
+			Opts.MaxC = i
+		}
+	}
 	if _, exists = os.LookupEnv("FFCVT_N"); Opts.NoExec || exists {
 		Opts.NoExec = true
 	}
@@ -279,6 +322,12 @@ func initVals() {
 	if _, exists = os.LookupEnv("FFCVT_FORCE"); Opts.Force || exists {
 		Opts.Force = true
 	}
+	if Opts.Debug == 0 ||
+		len(os.Getenv("FFCVT_DEBUG")) != 0 {
+		if i, err := strconv.Atoi(os.Getenv("FFCVT_DEBUG")); err == nil {
+			Opts.Debug = i
+		}
+	}
 	if len(Opts.FFMpeg) == 0 ||
 		len(os.Getenv("FFCVT_FFMPEG")) != 0 {
 		Opts.FFMpeg = os.Getenv("FFCVT_FFMPEG")
@@ -293,7 +342,7 @@ func initVals() {
 
 }
 
-const usageSummary = "  -t\ttarget type: webm/x265-opus/x264-mp3/wx/youtube/copy (FFCVT_T)\n  -ves\tvideo encoding method set (FFCVT_VES)\n  -aes\taudio encoding method set (FFCVT_AES)\n  -ses\tsubtitle encoding method set (FFCVT_SES)\n  -vep\tvideo encoding method prepend (FFCVT_VEP)\n  -aep\taudio encoding method prepend (FFCVT_AEP)\n  -sep\tsubtitle encoding method prepend (FFCVT_SEP)\n  -vea\tvideo encoding method append (FFCVT_VEA)\n  -aea\taudio encoding method append (FFCVT_AEA)\n  -abr\taudio bitrate (64k for opus, 256k for mp3) (FFCVT_ABR)\n  -crf\tthe CRF value: 0-51. Higher CRF gives lower quality\n\t (28 for x265, ~ 23 for x264) (FFCVT_CRF)\n\n  -d\tdirectory that hold input files (FFCVT_D)\n  -f\tinput file name (either -d or -f must be specified) (FFCVT_F)\n  -sym\tsymlinks will be processed as well (FFCVT_SYM)\n  -exts\textension list for all the files to be queued (FFCVT_EXTS)\n  -suf\tsuffix to the output file names (FFCVT_SUF)\n  -ext\textension for the output file (FFCVT_EXT)\n  -w\twork directory that hold output files (FFCVT_W)\n\n  -ac\tcopy audio codec (FFCVT_AC)\n  -vc\tcopy video codec (FFCVT_VC)\n  -an\tno audio, output video only (FFCVT_AN)\n  -vn\tno video, output audio only (FFCVT_VN)\n  -vss\tvideo: same size (FFCVT_VSS)\n  -C,Cut\tCut segment(s) out to keep. Specify in the form of start-[end],\n\tstrictly in the format of hh:mm:ss, and may repeat (FFCVT_C,CUT)\n  -S,Seg\tSplit video into multiple segments (strictly in format: hh:mm:ss) (FFCVT_S,SEG)\n  -Speed\tSpeed up/down video playback speed (e.g. 1.28) (FFCVT_SPEED)\n  -lang\tlanguage selection for audio stream extraction (FFCVT_LANG)\n  -sel\tsubtitle encoding language (language picked for reencoded video) (FFCVT_SEL)\n  -o\tmore options that will pass to ffmpeg program (FFCVT_O)\n  -ato-opus\taudio encode to opus, using -abr (FFCVT_ATO_OPUS)\n  -vto-x265\tvideo video encode to x265, using -crf (FFCVT_VTO_X265)\n\n  -p\tpar2create, create par2 files (in work directory) (FFCVT_P)\n  -nc\tno clobber, do not queue those already been converted (FFCVT_NC)\n  -n\tno exec, dry run (FFCVT_N)\n\n  -force\toverwrite any existing none-empty file (FFCVT_FORCE)\n  -debug\tdebugging level (FFCVT_DEBUG)\n  -ffmpeg\tffmpeg program executable name (FFCVT_FFMPEG)\n  -ffprobe\tffprobe program execution (FFCVT_FFPROBE)\n  -version\tprint version then exit (FFCVT_VERSION)\n\nDetails:\n\n"
+const usageSummary = "  -cfg\tcfg file to define your own targets: webm/wx/youtube etc (FFCVT_CFG)\n  -t\ttarget type: webm/x265-opus/x264-mp3/wx/youtube/copy, or empty (FFCVT_T)\n  -ves\tvideo encoding method set (FFCVT_VES)\n  -aes\taudio encoding method set (FFCVT_AES)\n  -ses\tsubtitle encoding method set (FFCVT_SES)\n  -vep\tvideo encoding method prepend (FFCVT_VEP)\n  -aep\taudio encoding method prepend (FFCVT_AEP)\n  -sep\tsubtitle encoding method prepend (FFCVT_SEP)\n  -vea\tvideo encoding method append (FFCVT_VEA)\n  -aea\taudio encoding method append (FFCVT_AEA)\n  -abr\taudio bitrate (64k for opus, 256k for mp3) (FFCVT_ABR)\n  -crf\tthe CRF value: 0-51. Higher CRF gives lower quality\n\t (28 for x265, ~ 23 for x264) (FFCVT_CRF)\n\n  -d\tdirectory that hold input files (FFCVT_D)\n  -f\tinput file name (either -d or -f must be specified) (FFCVT_F)\n  -sym\tsymlinks will be processed as well (FFCVT_SYM)\n  -exts\textension list for all the files to be queued (FFCVT_EXTS)\n  -suf\tsuffix to the output file names (FFCVT_SUF)\n  -ext\textension for the output file (FFCVT_EXT)\n  -w\twork directory that hold output files (FFCVT_W)\n\n  -ac\tcopy audio codec (FFCVT_AC)\n  -vc\tcopy video codec (FFCVT_VC)\n  -an\tno audio, output video only (FFCVT_AN)\n  -vn\tno video, output audio only (FFCVT_VN)\n  -vss\tvideo: same size (FFCVT_VSS)\n  -C,Cut\tCut segment(s) out to keep. Specify in the form of start-[end],\n\tstrictly in the format of hh:mm:ss, and may repeat (FFCVT_C,CUT)\n  -S,Seg\tSplit video into multiple segments (strictly in format: hh:mm:ss) (FFCVT_S,SEG)\n  -Speed\tSpeed up/down video playback speed (e.g. 1.28) (FFCVT_SPEED)\n  -K,karaoke\tAdd a karaoke audio track to .mp4 MTV (FFCVT_K,KARAOKE)\n  -tkf\tTranspose song's key from (e.g. C/C#/Db/D etc) (FFCVT_TKF)\n  -tkt\tTranspose song's key to (e.g. -tkf C -tkt Db) (FFCVT_TKT)\n  -lang\tlanguage selection for audio stream extraction (FFCVT_LANG)\n  -sel\tsubtitle encoding language (language picked for reencoded video) (FFCVT_SEL)\n  -o\tmore options that will pass to ffmpeg program (FFCVT_O)\n  -ato-opus\taudio encode to opus, using -abr (FFCVT_ATO_OPUS)\n  -vto-x265\tvideo video encode to x265, using -crf (FFCVT_VTO_X265)\n\n  -p\tpar2create, create par2 files (in work directory) (FFCVT_P)\n  -nc\tno clobber, do not queue those already been converted (FFCVT_NC)\n  -bt\tbreath time, interval between conversion to take a breath (FFCVT_BT)\n  -maxc\tmax conversion done each run (default no limit) (FFCVT_MAXC)\n  -n\tno exec, dry run (FFCVT_N)\n\n  -force\toverwrite any existing none-empty file (FFCVT_FORCE)\n  -debug\tdebugging level (FFCVT_DEBUG)\n  -ffmpeg\tffmpeg program executable name (FFCVT_FFMPEG)\n  -ffprobe\tffprobe program execution (FFCVT_FFPROBE)\n  -version\tprint version then exit (FFCVT_VERSION)\n\nDetails:\n\n"
 
 // Usage function shows help on commandline usage
 func Usage() {
diff --git a/debian/changelog b/debian/changelog
index 857691e..103dcb9 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,9 +1,10 @@
-ffcvt (1.7.6-2) UNRELEASED; urgency=medium
+ffcvt (1.11.1-1) UNRELEASED; urgency=medium
 
   * Remove constraints unnecessary since buster:
     + Build-Depends: Drop versioned constraint on ronn.
+  * New upstream release.
 
- -- Debian Janitor <janitor@jelmer.uk>  Thu, 03 Feb 2022 06:05:04 -0000
+ -- Debian Janitor <janitor@jelmer.uk>  Wed, 14 Jun 2023 03:46:37 -0000
 
 ffcvt (1.7.6-1) unstable; urgency=medium
 
diff --git a/ffcvt.go b/ffcvt.go
index ac01493..4b97b08 100644
--- a/ffcvt.go
+++ b/ffcvt.go
@@ -1,9 +1,3 @@
-////////////////////////////////////////////////////////////////////////////
-// Porgram: FfCvt
-// Purpose: ffmpeg convert wrapper tool
-// Authors: Tong Sun (c) 2015-2022, All rights reserved
-////////////////////////////////////////////////////////////////////////////
-
 /*
 
 Transcodes all videos in the given directory and all of it's subdirectories
@@ -11,13 +5,19 @@ using ffmpeg.
 
 */
 
+package main
+
+////////////////////////////////////////////////////////////////////////////
+// Porgram: FfCvt
+// Purpose: ffmpeg convert wrapper tool
+// Authors: Tong Sun (c) 2015-2023, All rights reserved
+////////////////////////////////////////////////////////////////////////////
+
 //go:generate sh -x ffcvt_cli.sh
 
 ////////////////////////////////////////////////////////////////////////////
 // Program start
 
-package main
-
 import (
 	"bytes"
 	"flag"
@@ -37,33 +37,85 @@ import (
 
 const _encodedExt = "_.mkv"
 
+// video encompass data around a single video
+type video struct {
+	name      string
+	size, sum int64
+	pct       int
+}
+
+// videoCol is the collection for the given set of videos
+type videoCol struct {
+	videos []video
+	sum    int64
+}
+
 ////////////////////////////////////////////////////////////////////////////
 // Global variables definitions
 
 var (
-	version = "1.7.5"
-	date    = "2022-01-02"
+	version = "1.11.1"
+	date    = "2023-05-26"
 
 	encodedExt string = _encodedExt
 	totalOrg   int64  = 1
 	totalNew   int64  = 1
-	videos     []string
+	vidCol     videoCol
 	workDirs   []string
 	cutOps     string = ""
+
+	transpFrom, transpTo string
 )
 
+var pitch = map[string]string{
+	"C":  "261.63",
+	"Db": "277.18",
+	"D":  "293.66",
+	"Eb": "311.13",
+	"E":  "329.63",
+	"F":  "349.23",
+	"Gb": "369.99",
+	"G":  "392.00",
+	"Ab": "415.30",
+	"A":  "440.00",
+	"Bb": "466.16",
+	"B":  "493.88",
+}
+
+func init() {
+	pitch["C#"] = pitch["Db"]
+	pitch["D#"] = pitch["Eb"]
+	pitch["F#"] = pitch["Gb"]
+	pitch["G#"] = pitch["Ab"]
+	pitch["A#"] = pitch["Bb"]
+}
+
 ////////////////////////////////////////////////////////////////////////////
 // Main
 
 func main() {
-	flag.Usage = Usage
-	flag.Parse()
 
+	// == Commandline handling
 	if Opts.PrintV {
-		fmt.Fprintf(os.Stderr, "%s\nVersion %s built on %s\n", progname, version, date)
+		fmt.Printf("%s version %s built on %s\n\n", progname, version, date)
+
+		ver, err := getVersion(Opts.FFMpeg)
+		if err != nil {
+			log.Fatalf("%s: Version check error - %s\n", progname, err.Error())
+		}
+		fmt.Printf("%s\n\n", ver)
+
+		// ffprobe, only use the first word
+		ver, err = getVersion((strings.Fields(Opts.FFProbe))[0])
+		if err != nil {
+			log.Fatalf("%s: Version check error - %s\n", progname, err.Error())
+		}
+		fmt.Printf("%s\n", ver)
+
 		os.Exit(0)
 	}
 
+	//fmt.Printf("%+v\n", Opts)
 	if len(Opts.Seg) > 0 {
 		// sanity check
 		_, err := time.Parse("15:04:05", Opts.Seg)
@@ -127,9 +179,29 @@ cut_ok:
 	if len(Opts.Directory)+len(Opts.File) < 1 {
 		Usage()
 	}
-	getDefault()
+	if len(Opts.TranspFrom) != 0 && len(Opts.TranspTo) == 0 ||
+		len(Opts.TranspFrom) == 0 && len(Opts.TranspTo) != 0 {
+		fmt.Fprintf(os.Stderr, "Signature transposition needs transpose keys for both from and to\n")
+		os.Exit(1)
+	}
+	if len(Opts.TranspFrom) != 0 {
+		var ok bool
+		if transpFrom, ok = pitch[Opts.TranspFrom]; !ok {
+			fmt.Fprintf(os.Stderr, "Unknown key signature to transpose from\n")
+			os.Exit(1)
+		}
+		if transpTo, ok = pitch[Opts.TranspTo]; !ok {
+			fmt.Fprintf(os.Stderr, "Unknown key signature to transpose to\n")
+			os.Exit(1)
+		}
+		fmt.Fprintf(os.Stderr, "Transposing from '%s' (%s) to '%s' (%s)\n",
+			Opts.TranspFrom, transpFrom, Opts.TranspTo, transpTo)
+	}
 
 	encodedExt = Opts.Ext
+	if Opts.Karaoke && encodedExt == _encodedExt {
+		encodedExt = "_karaoke.mp4"
+	}
 	// Sanity check
 	if Opts.WDirectory != "" {
 		// To error on the safe side -- when -d is not given but -f is,
@@ -165,6 +237,16 @@ cut_ok:
 		transcodeFile(Opts.File)
 	} else if Opts.Directory != "" {
 		filepath.Walk(Opts.Directory, visit)
+		// calculate (finished) percentage for each video
+		n := len(vidCol.videos)
+		if n > 0 {
+			totalSize := vidCol.sum
+			for i, _ := range vidCol.videos {
+				vidCol.videos[i].pct =
+					int((vidCol.videos[i].sum*1000/totalSize + 5) / 10)
+			}
+		}
+		//fmt.Printf("%v", vidCol)
 		transcodeVideos(startTime)
 	}
 	// par2 creating
@@ -181,22 +263,13 @@ cut_ok:
 }
 
 ////////////////////////////////////////////////////////////////////////////
-// Function definitions
-
-//==========================================================================
-// Directory & files handling
-
-func visit(path string, f os.FileInfo, err error) error {
-	if f.IsDir() {
-		return nil
-	}
-
-	appendVideo(Opts.Directory + string(os.PathSeparator) + path)
-	return nil
-}
+// Methods definitions
 
 // Append the video file to the list, unless it's encoded already
-func appendVideo(fname string) {
+// and duplicate none-video files to dest dir as well
+func (vidCol *videoCol) append(path string, size int64) {
+	fname := Opts.Directory + string(os.PathSeparator) + path
+
 	if Opts.WDirectory == "" && fname[len(fname)-5:] == encodedExt {
 		debug("Already-encoded file ignored: "+fname, 1)
 		return
@@ -204,7 +277,16 @@ func appendVideo(fname string) {
 
 	fext := strings.ToUpper(fname[len(fname)-4:])
 	if strings.Index(Opts.Exts, fext) < 0 {
-		debug("None-video file ignored: "+fname, 3)
+		// None-video files, dup to dest, hardlink 1st else copy
+		if Opts.WDirectory != "" {
+			src := Opts.Directory + "/" + fname
+			dst := Opts.WDirectory + "/" + fname
+			err := linkFile(src, dst)
+			if err != nil {
+				copyFile(src, dst)
+			}
+		}
+		debug("None-video file '"+fname+"' duplicated to dest dir.", 1)
 		return
 	}
 
@@ -218,9 +300,29 @@ func appendVideo(fname string) {
 		return
 	}
 
-	videos = append(videos, fname)
+	vidCol.sum += size
+	vidCol.videos = append(vidCol.videos,
+		video{name: fname, size: size, sum: vidCol.sum})
+
+}
+
+////////////////////////////////////////////////////////////////////////////
+// Function definitions
+
+//==========================================================================
+// Directory & files handling
+
+// visit will visit the source directory and queue videos found there to vidCol
+func visit(path string, f os.FileInfo, err error) error {
+	if f.IsDir() {
+		return nil
+	}
+
+	vidCol.append(path, f.Size())
+	return nil
 }
 
+// visitWDir will visit the work dir and add all directories there to workDirs
 func visitWDir(path string, f os.FileInfo, err error) error {
 	if !f.IsDir() {
 		return nil
@@ -231,6 +333,7 @@ func visitWDir(path string, f os.FileInfo, err error) error {
 	return nil
 }
 
+// createPar2s will create par2s files for each dir in workDirs
 func createPar2s(workDirs []string) {
 	fmt.Printf("\n== Creating par2 files\n\n")
 	for ii, dir := range workDirs {
@@ -241,7 +344,8 @@ func createPar2s(workDirs []string) {
 		os.Chdir(dir)
 		dirName := filepath.Base(dir)
 
-		cmd := []string{"par2create", "-u", "zz_" + dirName + ".par2", "*" + encodedExt}
+		// create par2s for all files within dest dir
+		cmd := []string{"par2create", "-u", "zz_" + dirName + ".par2", "*"}
 		debug(strings.Join(cmd, " "), 1)
 
 		out, err := exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
@@ -257,17 +361,30 @@ func createPar2s(workDirs []string) {
 
 // Transcode videos in the global videos array
 func transcodeVideos(startTime time.Time) {
-	videosTotal := len(videos)
-	for i, inputName := range videos {
+	videosTotal := len(vidCol.videos)
+	for i, v := range vidCol.videos {
+		inputName := v.name
 		videoNdx := i + 1
-		fmt.Printf("\n== Transcoding [%d/%d]: '%s'\n   under %s\n",
-			videoNdx, videosTotal, filepath.Base(inputName), filepath.Dir(inputName))
+
+		if Opts.NoClobber && fileExist(getOutputName(inputName)) {
+			debug("Encoded file exist for: "+inputName, 1)
+			continue
+		}
+
+		fmt.Printf("\n== Transcoding [%d/%d] (%d%%): '%s'\n   under %s\n",
+			videoNdx, videosTotal, v.pct, filepath.Base(inputName), filepath.Dir(inputName))
 		transcodeFile(inputName)
 		fmt.Printf("Time taken so far %s\n", time.Since(startTime))
+		if Opts.MaxC != 0 && videoNdx >= Opts.MaxC {
+			debug("Max conversion count reached.", 1)
+			os.Exit(0)
+		}
+		debug("Taking a breath for: "+Opts.BreathTime.String(), 1)
+		time.Sleep(Opts.BreathTime)
 		fmt.Printf("Finishing the remaining %d%% in %s\n",
-			(videosTotal-videoNdx)*100/videosTotal,
+			100-v.pct,
 			time.Duration(int64(float32(time.Since(startTime))*
-				float32(videosTotal-videoNdx)/float32(videoNdx))))
+				float32(100-v.pct)/float32(v.pct))))
 	}
 }
 
@@ -292,7 +409,7 @@ func transcodeFile(inputName string) {
 		debug(fsinfo, 4)
 		/*
 
-		   Cases when `-map`s are necessary
+		   Cases when `-map`s are necessary to deal with here
 
 		   - more than one audio stream, and we pick eng stream only
 		   - more than one subtitle stream, and
@@ -328,6 +445,22 @@ func transcodeFile(inputName string) {
 	args := []string{"-i", inputName}
 	args = append(args, strings.Fields(Opts.OptExtra)...)
 	args = encodeParametersS(encodeParametersA(encodeParametersV(args)))
+	if Opts.Karaoke {
+		args = append(args, "-filter_complex")
+		args = append(args, "[0:1]pan=mono|c0=c0-c1[a]")
+		args = append(args, "-map")
+		args = append(args, "0:0")
+		args = append(args, "-map")
+		args = append(args, "[a]")
+		args = append(args, "-map")
+		args = append(args, "0:1")
+	}
+	if len(transpFrom) != 0 {
+		args = append(args, "-af")
+		af := fmt.Sprintf("atempo=%s/%s,asetrate=44100*%[2]s/%[1]s",
+			transpFrom, transpTo)
+		args = append(args, af)
+	}
 	if Opts.Force {
 		args = append(args, "-y")
 	}
@@ -445,6 +578,21 @@ func probeFile(inputName string) (string, error) {
 	return string(out.Bytes()), err
 }
 
+func getVersion(execName string) (string, error) {
+	out := &bytes.Buffer{}
+
+	cmd := exec.Command(execName, "-version")
+	cmd.Stdout = out
+	cmd.Stderr = out
+	err := cmd.Run()
+	if err != nil {
+		return "", err
+	}
+	// OK, then only return the first two lines
+	lines := strings.Split(string(out.Bytes()), "\n")
+	return strings.Join(lines[:2], "\n"), nil
+}
+
 // Returns the encode parameters for Subtitle
 func encodeParametersS(args []string) []string {
 	if Opts.SEP != "" {
@@ -472,6 +620,7 @@ func encodeParametersA(args []string) []string {
 	if Opts.A2Opus {
 		Opts.AES = "libopus"
 	}
+	//fmt.Printf("] %v + AE: '%s' '%s'\n", args, Opts.AES, Opts.ABR)
 	if Opts.AES != "" {
 		args = append(args, "-c:a", Opts.AES)
 	}
diff --git a/ffcvt.json b/ffcvt.json
new file mode 100644
index 0000000..98c87b8
--- /dev/null
+++ b/ffcvt.json
@@ -0,0 +1,53 @@
+{
+  "": {
+    "Ext": "_.mkv"
+  },
+  "copy": {
+    "AES": "copy",
+    "VES": "copy",
+    "SES": "-c:s copy",
+    "ABR": "64k",
+    "CRF": "42",
+    "Ext": "_.mkv"
+  },
+  "webm": {
+    "AES": "libopus",
+    "VES": "libvpx-vp9",
+    "SES": "-c:s copy",
+    "ABR": "64k",
+    "CRF": "42",
+    "Ext": "_.mkv"
+  },
+  "x265-opus": {
+    "AES": "libopus",
+    "VES": "libx265",
+    "ABR": "64k",
+    "CRF": "28",
+    "Ext": "_.mkv"
+  },
+  "wx": {
+    "AES": "aac",
+    "AEA": "-q:a 3",
+    "VES": "libx264",
+    "ABR": "48k",
+    "CRF": "33",
+    "Ext": "_.m4v"
+  },
+  "x264-mp3": {
+    "AES": "libmp3lame",
+    "AEA": "-q:a 3",
+    "VES": "libx264",
+    "ABR": "256k",
+    "CRF": "23",
+    "Ext": "_.mp4"
+  },
+  "youtube": {
+    "AES": "libvorbis",
+    "AEA": "-q:a 5",
+    "VES": "libx264",
+    "VEA": "-pix_fmt yuv420p",
+    "ABR": "",
+    "CRF": "20",
+    "Ext": "_.avi"
+  }
+}
diff --git a/ffcvt_cli.sh b/ffcvt_cli.sh
index 6b84a0b..85944a1 100755
--- a/ffcvt_cli.sh
+++ b/ffcvt_cli.sh
@@ -6,4 +6,4 @@ templateFile=$GOPATH/src/github.com/go-easygen/easygen/test/commandlineFlag
   exit 1
 }
 
-easygen $templateFile ffcvt_cli | tee /tmp/ffcvt_cli.go | sed '/\tVES\t\tstring/{ N; N; N; N; N; N; N; N; N; s|^.*$|\tEncoding\t// anonymous field to hold encoding values|; }; /\tExt\t\tstring/d; /flag.MFlagsVar/{s/flag.MFlagsVar/flag.Var/; s/, "",/,/; } ' | gofmt > config.go
+easygen $templateFile ffcvt_cli | tee /tmp/ffcvt_cli.go | sed '/\tVES\t\tstring/{ N; N; N; N; N; N; N; N; N; s|^.*$|\tEncoding\t// anonymous field to hold encoding values|; }; /\tExt\t\tstring/d; /flag.MFlagsVar/{s/flag.MFlagsVar/flag.Var/; s/, "",/,/; } ' | goimports > config.go
diff --git a/ffcvt_cli.yaml b/ffcvt_cli.yaml
index 7a07518..abebad2 100644
--- a/ffcvt_cli.yaml
+++ b/ffcvt_cli.yaml
@@ -29,12 +29,18 @@ StructVar: Opts
 Options:
 
   # Basic fields for encoding
-  
+
+  - Name: Cfg
+    Type: string
+    Flag: cfg
+    Value: '""'
+    Usage: "cfg file to define your own targets: webm/wx/youtube etc"
+
   - Name: Target
     Type: string
     Flag: t
     Value: '"webm"'
-    Usage: "target type: webm/x265-opus/x264-mp3/wx/youtube/copy"
+    Usage: "target type: webm/x265-opus/x264-mp3/wx/youtube/copy, or empty"
 
   - Name: VES
     Type: string
@@ -198,6 +204,24 @@ Options:
     Value: '""'
     Usage: "Speed up/down video playback speed (e.g. 1.28)"
 
+  - Name: Karaoke
+    Type: bool
+    Flag: K,karaoke
+    Value: false
+    Usage: "Add a karaoke audio track to .mp4 MTV"
+
+  - Name: TranspFrom
+    Type: string
+    Flag: tkf
+    Value: '""'
+    Usage: "Transpose song's key from (e.g. C/C#/Db/D etc)"
+
+  - Name: TranspTo
+    Type: string
+    Flag: tkt
+    Value: '""'
+    Usage: "Transpose song's key to (e.g. -tkf C -tkt Db)"
+
   - Name: Lang
     Type: string
     Flag: lang
@@ -242,6 +266,18 @@ Options:
     Value: false
     Usage: "no clobber, do not queue those already been converted"
 
+  - Name: BreathTime
+    Type: duration
+    Flag: bt
+    Value: '120*time.Second'
+    Usage: "breath time, interval between conversion to take a breath"
+
+  - Name: MaxC
+    Type: int
+    Flag: maxc
+    Value: 0
+    Usage: max conversion done each run (default no limit)
+
   - Name: NoExec
     Type: bool
     Flag: "n"
diff --git a/ffcvt_proj.yaml b/ffcvt_proj.yaml
deleted file mode 100644
index 7aa9235..0000000
--- a/ffcvt_proj.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-Wireframe:
-  Proj: ffcvt
-  Desc: ffmpeg convert wrapper tool
-  Lang: Go
-  Vendor: suntong
-  Author: Tong Sun <suntong@cpan.org>
-  License: MIT
-  
diff --git a/file_dup.go b/file_dup.go
new file mode 100644
index 0000000..14130eb
--- /dev/null
+++ b/file_dup.go
@@ -0,0 +1,39 @@
+// Copy or hardlink a file in Go
+
+package main
+
+import (
+	"io"
+	"os"
+)
+
+func mainSample() {
+	copyFile("test.txt", "test_copy.txt")
+	copyFile("test_copy.txt", "test_link.txt")
+}
+
+// hardlink a file
+func linkFile(src, dst string) error {
+	return os.Link(src, dst)
+}
+
+// Copy a file
+func copyFile(src, dst string) {
+	srcFile, err := os.Open(src)
+	check(err)
+	defer srcFile.Close()
+
+	destFile, err := os.Create(dst) // creates if file doesn't exist
+	check(err)
+	defer destFile.Close()
+
+	_, err = io.Copy(destFile, srcFile) // check first var for number of bytes copied
+	check(err)
+
+	err = destFile.Sync()
+	check(err)
+}
+
+func check(err error) {
+	checkError(err)
+}
diff --git a/preset-large.png b/preset-large.png
deleted file mode 100644
index 4735699..0000000
Binary files a/preset-large.png and /dev/null differ
diff --git a/preset-small.png b/preset-small.png
deleted file mode 100644
index 1720088..0000000
Binary files a/preset-small.png and /dev/null differ
diff --git a/structEnc.go b/structEnc.go
index db9a9be..ec703f4 100644
--- a/structEnc.go
+++ b/structEnc.go
@@ -1,6 +1,12 @@
 package main
 
-import "log"
+import (
+	"embed"
+	"encoding/json"
+	"flag"
+	"io/ioutil"
+	"log"
+)
 
 ////////////////////////////////////////////////////////////////////////////
 // Constant and data type/structure definitions
@@ -29,82 +35,48 @@ type Encoding struct {
 var Defaults map[string]Encoding
 
 func init() {
-	initVars()
+	initVars() // define flag vars
 	debug(Opts.CRF, 3)
-	initDefaults()
+
+	flag.Usage = Usage
+	flag.Parse()
+
+	initDefaults() // read & populate defaults from ffcvt.json
+	debug(Opts.CRF, 3)
+
+	getDefault() // re-read defaults based on cli -cfg & -t
+	//fmt.Fprintf(os.Stderr, "Defaults: '%+v'\n", Defaults)
 	debug(Opts.CRF, 3)
+	flag.Parse() // update Opts again from cli to overwrite defaults if necessary
+	initVals()   // update as per cli settings
 }
 
-func initDefaults() {
-
-	Defaults = map[string]Encoding{
-		"copy": {
-			AES: "copy",
-			VES: "copy",
-			SES: "-c:s copy",
-			ABR: "64k",
-			CRF: "42",
-			Ext: _encodedExt,
-		},
-		"webm": {
-			AES: "libopus",
-			VES: "libvpx-vp9",
-			SES: "-c:s copy",
-			ABR: "64k",
-			CRF: "42",
-			Ext: _encodedExt,
-		},
-		"x265-opus": {
-			AES: "libopus",
-			VES: "libx265",
-			ABR: "64k",
-			CRF: "28",
-			Ext: _encodedExt,
-		},
-		"wx": {
-			AES: "aac",
-			AEA: "-q:a 3",
-			VES: "libx264",
-			ABR: "48k",
-			CRF: "33",
-			Ext: "_.m4v",
-		},
-		"x264-mp3": {
-			AES: "libmp3lame",
-			AEA: "-q:a 3",
-			VES: "libx264",
-			ABR: "256k",
-			CRF: "23",
-			Ext: "_.mp4",
-		},
-		"youtube": {
-			// https://trac.ffmpeg.org/wiki/Encode/YouTube
-			// https://trac.ffmpeg.org/wiki/Encode/HighQualityAudio
-			AES: "libvorbis",
-			AEA: "-q:a 5",
-			VES: "libx264",
-			VEA: "-pix_fmt yuv420p",
-			ABR: "",
-			CRF: "20",
-			Ext: "_.avi",
-		},
-	}
+//go:embed ffcvt.json
+// see https://pkg.go.dev/embed
+var f embed.FS
 
+func initDefaults() {
+	data, err := f.ReadFile("ffcvt.json")
+	checkError(err)
+	json.Unmarshal(data, &Defaults)
 }
 
 func getDefault() {
+	if Opts.Cfg != "" {
+		data, err := ioutil.ReadFile(Opts.Cfg)
+		checkError(err)
+		err = json.Unmarshal(data, &Defaults)
+		checkError(err)
+	}
 	if encDefault, ok := Defaults[Opts.Target]; ok {
 		// debug(encDefault.Ext, 2)
 		Opts.Encoding = encDefault
 		// debug(Opts.Encoding.Ext, 2)
 		// debug(Opts.Ext, 2)
 	} else {
-		log.Fatal(progname + " Error: Wrong target option passed to -t.")
+		log.Fatalf("[%s] Error: Wrong target option passed to -t.", progname)
 	}
 
-	initVals()
-	debug(Opts.CRF, 3)
-
 	if Opts.Suffix != "" {
 		Opts.Suffix = "_" + Opts.Suffix
 	}
diff --git a/test/ffcvt_test.txt b/test/ffcvt_test.txt
index 051b7c6..1ea4628 100644
--- a/test/ffcvt_test.txt
+++ b/test/ffcvt_test.txt
@@ -4,7 +4,8 @@ Usage:
 
 Flags:
 
-  -t	target type: webm/x265-opus/x264-mp3/wx/youtube/copy (FFCVT_T)
+  -cfg	cfg file to define your own targets: webm/wx/youtube etc (FFCVT_CFG)
+  -t	target type: webm/x265-opus/x264-mp3/wx/youtube/copy, or empty (FFCVT_T)
   -ves	video encoding method set (FFCVT_VES)
   -aes	audio encoding method set (FFCVT_AES)
   -ses	subtitle encoding method set (FFCVT_SES)
@@ -34,6 +35,9 @@ Flags:
 	strictly in the format of hh:mm:ss, and may repeat (FFCVT_C,CUT)
   -S,Seg	Split video into multiple segments (strictly in format: hh:mm:ss) (FFCVT_S,SEG)
   -Speed	Speed up/down video playback speed (e.g. 1.28) (FFCVT_SPEED)
+  -K,karaoke	Add a karaoke audio track to .mp4 MTV (FFCVT_K,KARAOKE)
+  -tkf	Transpose song's key from (e.g. C/C#/Db/D etc) (FFCVT_TKF)
+  -tkt	Transpose song's key to (e.g. -tkf C -tkt Db) (FFCVT_TKT)
   -lang	language selection for audio stream extraction (FFCVT_LANG)
   -sel	subtitle encoding language (language picked for reencoded video) (FFCVT_SEL)
   -o	more options that will pass to ffmpeg program (FFCVT_O)
@@ -42,6 +46,8 @@ Flags:
 
   -p	par2create, create par2 files (in work directory) (FFCVT_P)
   -nc	no clobber, do not queue those already been converted (FFCVT_NC)
+  -bt	breath time, interval between conversion to take a breath (FFCVT_BT)
+  -maxc	max conversion done each run (default no limit) (FFCVT_MAXC)
   -n	no exec, dry run (FFCVT_N)
 
   -force	overwrite any existing none-empty file (FFCVT_FORCE)
@@ -58,6 +64,7 @@ Details:
   -Cut value
     	Cut segment(s) out to keep. Specify in the form of start-[end],
     		strictly in the format of hh:mm:ss, and may repeat
+  -K	Add a karaoke audio track to .mp4 MTV
   -S string
     	Split video into multiple segments (strictly in format: hh:mm:ss)
   -Seg string
@@ -78,6 +85,10 @@ Details:
     	no audio, output video only
   -ato-opus
     	audio encode to opus, using -abr
+  -bt duration
+    	breath time, interval between conversion to take a breath (default 2m0s)
+  -cfg string
+    	cfg file to define your own targets: webm/wx/youtube etc
   -crf string
     	the CRF value: 0-51. Higher CRF gives lower quality
     		 (28 for x265, ~ 23 for x264)
@@ -97,8 +108,12 @@ Details:
     	ffprobe program execution (default "ffprobe -print_format flat")
   -force
     	overwrite any existing none-empty file
+  -karaoke
+    	Add a karaoke audio track to .mp4 MTV
   -lang string
     	language selection for audio stream extraction (default "eng")
+  -maxc int
+    	max conversion done each run (default no limit)
   -n	no exec, dry run
   -nc
     	no clobber, do not queue those already been converted
@@ -116,7 +131,11 @@ Details:
   -sym
     	symlinks will be processed as well
   -t string
-    	target type: webm/x265-opus/x264-mp3/wx/youtube/copy (default "webm")
+    	target type: webm/x265-opus/x264-mp3/wx/youtube/copy, or empty (default "webm")
+  -tkf string
+    	Transpose song's key from (e.g. C/C#/Db/D etc)
+  -tkt string
+    	Transpose song's key to (e.g. -tkf C -tkt Db)
   -vc
     	copy video codec
   -vea string
@@ -137,122 +156,327 @@ Details:
     	work directory that hold output files
 
 To reduce output, use `-debug 0`, e.g., `ffcvt -force -debug 0 -f testf.mp4 ...`
+# Test transcoding single file
 
 == Transcoding: StreamSample.mkv
 ffcvt: to execute -
   ffmpeg -i StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/StreamSample.mkv
 
+Transcoding completed in xxx ms
 Org Size: 0 MB
 New Size: 0 MB
 Saved:    0%
+# Test transcoding different target types
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/StreamSample.mkv
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/StreamSample.mkv
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k /tmp/StreamSample.mkv
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k /tmp/StreamSample.mkv
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -c:v libx264 -x264-params crf=23 -c:a libmp3lame -b:a 256k -q:a 3 /tmp/StreamSample.mp4
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -c:v libx264 -x264-params crf=23 -c:a libmp3lame -b:a 256k -q:a 3 /tmp/StreamSample.mp4
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -c:v libx264 -x264-params crf=33 -c:a aac -b:a 48k -q:a 3 /tmp/StreamSample.m4v
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -c:v libx264 -x264-params crf=33 -c:a aac -b:a 48k -q:a 3 /tmp/StreamSample.m4v
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -c:v libx264 -x264-params crf=20 -pix_fmt yuv420p -c:a libvorbis -q:a 5 /tmp/StreamSample.avi
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -c:v libx264 -x264-params crf=20 -pix_fmt yuv420p -c:a libvorbis -q:a 5 /tmp/StreamSample.avi
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -c:v copy -c:a copy -b:a 64k -c:s copy /tmp/StreamSample.mkv
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -c:v copy -c:a copy -b:a 64k -c:s copy /tmp/StreamSample.mkv
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+# Test adding karaoke audio track
+
+== Transcoding: test1.avi
+] ffmpeg -i test1.avi -filter_complex [0:1]pan=mono|c0=c0-c1[a] -map 0:0 -map [a] -map 0:1 test1_karaoke.mp4
+ffcvt: to execute -
+  ffmpeg -i test1.avi -filter_complex [0:1]pan=mono|c0=c0-c1[a] -map 0:0 -map [a] -map 0:1 test1_karaoke.mp4
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+
+== Transcoding: test1.avi
+] ffmpeg -i test1.avi -filter_complex [0:1]pan=mono|c0=c0-c1[a] -map 0:0 -map [a] -map 0:1 test1_k.avi
+ffcvt: to execute -
+  ffmpeg -i test1.avi -filter_complex [0:1]pan=mono|c0=c0-c1[a] -map 0:0 -map [a] -map 0:1 test1_k.avi
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+# Test transposing different keys
+Transposing from 'F' (349.23) to 'D' (293.66)
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -vn -c:a libmp3lame -b:a 256k -q:a 3 -af atempo=349.23/293.66,asetrate=44100*293.66/349.23 /tmp/StreamSample.mp4
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -vn -c:a libmp3lame -b:a 256k -q:a 3 -af atempo=349.23/293.66,asetrate=44100*293.66/349.23 /tmp/StreamSample.mp4
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+Transposing from 'F#' (369.99) to 'Db' (277.18)
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -vn -c:a libmp3lame -b:a 72k -q:a 3 -af atempo=369.99/277.18,asetrate=44100*277.18/369.99 /tmp/StreamSample.mp4
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -vn -c:a libmp3lame -b:a 72k -q:a 3 -af atempo=369.99/277.18,asetrate=44100*277.18/369.99 /tmp/StreamSample.mp4
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+Transposing from 'Gb' (369.99) to 'A#' (466.16)
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -vn -c:a libmp3lame -b:a 72k -af atempo=369.99/466.16,asetrate=44100*466.16/369.99 StreamSample_.mp3
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -vn -c:a libmp3lame -b:a 72k -af atempo=369.99/466.16,asetrate=44100*466.16/369.99 StreamSample_.mp3
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+# Test -sym control
+] None-video file './ffcvt_test.txt' duplicated to dest dir.
+] None-video file './test-all.sh' duplicated to dest dir.
 ] Skip symlink file: ./test1.avi
 ] Skip symlink file: ./test2.avi
 ] Skip symlink file: ./test3.webm
 
-== Transcoding [1/1]: 'StreamSample.mkv'
+== Transcoding [1/1] (100%): 'StreamSample.mkv'
    under .
 ] ffmpeg -i ./StreamSample.mkv -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./StreamSample_.mkv
 ffcvt: to execute -
   ffmpeg -i ./StreamSample.mkv -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./StreamSample_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
+Transcoding completed in xxx ms
 Org Size: 0 MB
 New Size: 0 MB
 Saved:    0%
+] None-video file './ffcvt_test.txt' duplicated to dest dir.
+] None-video file './test-all.sh' duplicated to dest dir.
 
-== Transcoding [1/4]: 'StreamSample.mkv'
+== Transcoding [1/4] (100%): 'StreamSample.mkv'
    under .
 ] ffmpeg -i ./StreamSample.mkv -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./StreamSample_.mkv
 ffcvt: to execute -
   ffmpeg -i ./StreamSample.mkv -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./StreamSample_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
-== Transcoding [2/4]: 'test1.avi'
+== Transcoding [2/4] (100%): 'test1.avi'
    under .
 ] ffmpeg -i ./test1.avi -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./test1_.mkv
 ffcvt: to execute -
   ffmpeg -i ./test1.avi -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./test1_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
-== Transcoding [3/4]: 'test2.avi'
+== Transcoding [3/4] (100%): 'test2.avi'
    under .
 ] ffmpeg -i ./test2.avi -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./test2_.mkv
 ffcvt: to execute -
   ffmpeg -i ./test2.avi -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./test2_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
-== Transcoding [4/4]: 'test3.webm'
+== Transcoding [4/4] (100%): 'test3.webm'
    under .
 ] ffmpeg -i ./test3.webm -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./test3_.mkv
 ffcvt: to execute -
   ffmpeg -i ./test3.webm -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./test3_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
+Transcoding completed in xxx ms
 Org Size: 0 MB
 New Size: 0 MB
 Saved:    0%
+] None-video file './ffcvt_test.txt' duplicated to dest dir.
+] None-video file './test-all.sh' duplicated to dest dir.
 ] Skip symlink file: ./test1.avi
 ] Skip symlink file: ./test2.avi
 ] Skip symlink file: ./test3.webm
 
-== Transcoding [1/1]: 'StreamSample.mkv'
+== Transcoding [1/1] (100%): 'StreamSample.mkv'
    under .
 ] ffmpeg -i ./StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./StreamSample_.mkv
 ffcvt: to execute -
   ffmpeg -i ./StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./StreamSample_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
+Transcoding completed in xxx ms
 Org Size: 0 MB
 New Size: 0 MB
 Saved:    0%
+] None-video file './ffcvt_test.txt' duplicated to dest dir.
+] None-video file './test-all.sh' duplicated to dest dir.
 
-== Transcoding [1/4]: 'StreamSample.mkv'
+== Transcoding [1/4] (100%): 'StreamSample.mkv'
    under .
 ] ffmpeg -i ./StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./StreamSample_.mkv
 ffcvt: to execute -
   ffmpeg -i ./StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./StreamSample_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
-== Transcoding [2/4]: 'test1.avi'
+== Transcoding [2/4] (100%): 'test1.avi'
    under .
 ] ffmpeg -i ./test1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test1_.mkv
 ffcvt: to execute -
   ffmpeg -i ./test1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test1_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
-== Transcoding [3/4]: 'test2.avi'
+== Transcoding [3/4] (100%): 'test2.avi'
    under .
 ] ffmpeg -i ./test2.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test2_.mkv
 ffcvt: to execute -
   ffmpeg -i ./test2.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test2_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
-== Transcoding [4/4]: 'test3.webm'
+== Transcoding [4/4] (100%): 'test3.webm'
    under .
 ] ffmpeg -i ./test3.webm -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test3_.mkv
 ffcvt: to execute -
   ffmpeg -i ./test3.webm -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test3_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
+Transcoding completed in xxx ms
 Org Size: 0 MB
 New Size: 0 MB
 Saved:    0%
+] None-video file './ffcvt_test.txt' duplicated to dest dir.
+] None-video file './test-all.sh' duplicated to dest dir.
+
+== Transcoding [1/4] (100%): 'StreamSample.mkv'
+   under .
+] ffmpeg -i ./StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./StreamSample_.mkv
+ffcvt: to execute -
+  ffmpeg -i ./StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./StreamSample_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
+
+== Transcoding [2/4] (100%): 'test1.avi'
+   under .
+] ffmpeg -i ./test1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test1_.mkv
+ffcvt: to execute -
+  ffmpeg -i ./test1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test1_.mkv
+Time taken so far xxx ms
+] Max conversion count reached.
+] None-video file './ffcvt_test.txt' duplicated to dest dir.
+] None-video file './test-all.sh' duplicated to dest dir.
+
+== Transcoding [1/4] (100%): 'StreamSample.mkv'
+   under .
+] ffmpeg -i ./StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./StreamSample_.mkv
+ffcvt: to execute -
+  ffmpeg -i ./StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./StreamSample_.mkv
+Time taken so far xxx ms
+] Max conversion count reached.
 ] Transcoding to /tmp/test
+] None-video file './ffcvt_test.txt' duplicated to dest dir.
+] None-video file './test-all.sh' duplicated to dest dir.
 
-== Transcoding [1/4]: 'StreamSample.mkv'
+== Transcoding [1/4] (100%): 'StreamSample.mkv'
    under .
 ] ffmpeg -i ./StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test/StreamSample.mkv
 ffcvt: to execute -
   ffmpeg -i ./StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test/StreamSample.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
-== Transcoding [2/4]: 'test1.avi'
+== Transcoding [2/4] (100%): 'test1.avi'
    under .
 ] ffmpeg -i ./test1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test/test1.mkv
 ffcvt: to execute -
   ffmpeg -i ./test1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test/test1.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
-== Transcoding [3/4]: 'test2.avi'
+== Transcoding [3/4] (100%): 'test2.avi'
    under .
 ] ffmpeg -i ./test2.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test/test2.mkv
 ffcvt: to execute -
   ffmpeg -i ./test2.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test/test2.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
-== Transcoding [4/4]: 'test3.webm'
+== Transcoding [4/4] (100%): 'test3.webm'
    under .
 ] ffmpeg -i ./test3.webm -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test/test3.mkv
 ffcvt: to execute -
   ffmpeg -i ./test3.webm -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test/test3.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
+Transcoding completed in xxx ms
 Org Size: 0 MB
 New Size: 0 MB
 Saved:    0%
diff --git a/test/test-all.sh b/test/test-all.sh
index d0b0963..e160e5f 100755
--- a/test/test-all.sh
+++ b/test/test-all.sh
@@ -9,23 +9,43 @@ FFCVT=../ffcvt
 
 $FFCVT -version
 
-echo '- Test (config.go) cli help output'
+echo
+echo '# Test (config.go) cli help output'
 $FFCVT > /tmp/ffcvt_test.txt 2>&1
 
-echo - Test transcoding single file
+echo '# Test transcoding single file' | tee -a /tmp/ffcvt_test.txt
 $FFCVT -n -debug 0 -f StreamSample.mkv -w /tmp >> /tmp/ffcvt_test.txt 2>&1
 
-echo - Test -sym control
-$FFCVT -t x265-opus -n -d . >> /tmp/ffcvt_test.txt 2>&1
-$FFCVT -t x265-opus -n -d . -sym  >> /tmp/ffcvt_test.txt 2>&1
+echo '# Test transcoding different target types' | tee -a /tmp/ffcvt_test.txt
+$FFCVT -t webm -n -f StreamSample.mkv -w /tmp >> /tmp/ffcvt_test.txt 2>&1
+$FFCVT -t x265-opus -n -f StreamSample.mkv -w /tmp >> /tmp/ffcvt_test.txt 2>&1
+$FFCVT -t x264-mp3 -n -f StreamSample.mkv -w /tmp >> /tmp/ffcvt_test.txt 2>&1
+$FFCVT -t wx -n -f StreamSample.mkv -w /tmp >> /tmp/ffcvt_test.txt 2>&1
+$FFCVT -t youtube -n -f StreamSample.mkv -w /tmp >> /tmp/ffcvt_test.txt 2>&1
+$FFCVT -t copy -n -f StreamSample.mkv -w /tmp >> /tmp/ffcvt_test.txt 2>&1
 
-$FFCVT -n -d .  >> /tmp/ffcvt_test.txt 2>&1
-$FFCVT -n -d . -sym  >> /tmp/ffcvt_test.txt 2>&1
+echo '# Test adding karaoke audio track' | tee -a /tmp/ffcvt_test.txt
+$FFCVT -n -f test1.avi -t '' -K >> /tmp/ffcvt_test.txt 2>&1
+$FFCVT -n -f test1.avi -t '' -karaoke -ext _k.avi >> /tmp/ffcvt_test.txt 2>&1
 
-$FFCVT -n -sym -debug 2 -d . -w /tmp >> /tmp/ffcvt_test.txt 2>&1
+echo '# Test transposing different keys' | tee -a /tmp/ffcvt_test.txt
+$FFCVT -t x264-mp3 -n -f StreamSample.mkv -w /tmp -tkf F -tkt D -vn >> /tmp/ffcvt_test.txt 2>&1
+$FFCVT -t x264-mp3 -n -f StreamSample.mkv -w /tmp -tkf 'F#' -tkt Db -vn -abr 72k >> /tmp/ffcvt_test.txt 2>&1
+$FFCVT -t '' -n -f StreamSample.mkv -tkf Gb -tkt 'A#' -vn -abr 72k -aes libmp3lame -ext _.mp3 >> /tmp/ffcvt_test.txt 2>&1
 
-echo - Compare test results
-sed -i '/ [0-9.]*[mΒ΅]*s$/d' /tmp/ffcvt_test.txt
+echo '# Test -sym control' | tee -a /tmp/ffcvt_test.txt
+$FFCVT -t x265-opus -n -bt 0.1s -d . >> /tmp/ffcvt_test.txt 2>&1
+$FFCVT -t x265-opus -n -bt 0.1s -d . -sym  >> /tmp/ffcvt_test.txt 2>&1
+
+$FFCVT -n -bt 0.1s -d .  >> /tmp/ffcvt_test.txt 2>&1
+$FFCVT -n -bt 0.1s -d . -sym  >> /tmp/ffcvt_test.txt 2>&1
+$FFCVT -n -bt 0.1s -d . -sym -maxc 2  >> /tmp/ffcvt_test.txt 2>&1
+FFCVT_MAXC=1 $FFCVT -n -bt 0.1s -d . -sym  >> /tmp/ffcvt_test.txt 2>&1
+
+$FFCVT -n -sym -debug 2 -bt 0.1s -d . -w /tmp >> /tmp/ffcvt_test.txt 2>&1
+
+echo '# Compare test results, 0 means AOK:'
+sed -i '/ [0-9.]*[nmΒ΅]*s$/s// xxx ms/' /tmp/ffcvt_test.txt
 diff -wU 1 ffcvt_test.txt /tmp/ffcvt_test.txt
 
 ret=$?
diff --git a/test2/ffcvt_test.txt b/test2/ffcvt_test.txt
index d1424c2..aa5d659 100644
--- a/test2/ffcvt_test.txt
+++ b/test2/ffcvt_test.txt
@@ -4,7 +4,8 @@ Usage:
 
 Flags:
 
-  -t	target type: webm/x265-opus/x264-mp3/wx/youtube/copy (FFCVT_T)
+  -cfg	cfg file to define your own targets: webm/wx/youtube etc (FFCVT_CFG)
+  -t	target type: webm/x265-opus/x264-mp3/wx/youtube/copy, or empty (FFCVT_T)
   -ves	video encoding method set (FFCVT_VES)
   -aes	audio encoding method set (FFCVT_AES)
   -ses	subtitle encoding method set (FFCVT_SES)
@@ -34,6 +35,9 @@ Flags:
 	strictly in the format of hh:mm:ss, and may repeat (FFCVT_C,CUT)
   -S,Seg	Split video into multiple segments (strictly in format: hh:mm:ss) (FFCVT_S,SEG)
   -Speed	Speed up/down video playback speed (e.g. 1.28) (FFCVT_SPEED)
+  -K,karaoke	Add a karaoke audio track to .mp4 MTV (FFCVT_K,KARAOKE)
+  -tkf	Transpose song's key from (e.g. C/C#/Db/D etc) (FFCVT_TKF)
+  -tkt	Transpose song's key to (e.g. -tkf C -tkt Db) (FFCVT_TKT)
   -lang	language selection for audio stream extraction (FFCVT_LANG)
   -sel	subtitle encoding language (language picked for reencoded video) (FFCVT_SEL)
   -o	more options that will pass to ffmpeg program (FFCVT_O)
@@ -42,6 +46,8 @@ Flags:
 
   -p	par2create, create par2 files (in work directory) (FFCVT_P)
   -nc	no clobber, do not queue those already been converted (FFCVT_NC)
+  -bt	breath time, interval between conversion to take a breath (FFCVT_BT)
+  -maxc	max conversion done each run (default no limit) (FFCVT_MAXC)
   -n	no exec, dry run (FFCVT_N)
 
   -force	overwrite any existing none-empty file (FFCVT_FORCE)
@@ -58,6 +64,7 @@ Details:
   -Cut value
     	Cut segment(s) out to keep. Specify in the form of start-[end],
     		strictly in the format of hh:mm:ss, and may repeat
+  -K	Add a karaoke audio track to .mp4 MTV
   -S string
     	Split video into multiple segments (strictly in format: hh:mm:ss)
   -Seg string
@@ -78,6 +85,10 @@ Details:
     	no audio, output video only
   -ato-opus
     	audio encode to opus, using -abr
+  -bt duration
+    	breath time, interval between conversion to take a breath (default 2m0s)
+  -cfg string
+    	cfg file to define your own targets: webm/wx/youtube etc
   -crf string
     	the CRF value: 0-51. Higher CRF gives lower quality
     		 (28 for x265, ~ 23 for x264)
@@ -97,8 +108,12 @@ Details:
     	ffprobe program execution (default "ffprobe -print_format flat")
   -force
     	overwrite any existing none-empty file
+  -karaoke
+    	Add a karaoke audio track to .mp4 MTV
   -lang string
     	language selection for audio stream extraction (default "eng")
+  -maxc int
+    	max conversion done each run (default no limit)
   -n	no exec, dry run
   -nc
     	no clobber, do not queue those already been converted
@@ -116,7 +131,11 @@ Details:
   -sym
     	symlinks will be processed as well
   -t string
-    	target type: webm/x265-opus/x264-mp3/wx/youtube/copy (default "webm")
+    	target type: webm/x265-opus/x264-mp3/wx/youtube/copy, or empty (default "webm")
+  -tkf string
+    	Transpose song's key from (e.g. C/C#/Db/D etc)
+  -tkt string
+    	Transpose song's key to (e.g. -tkf C -tkt Db)
   -vc
     	copy video codec
   -vea string
@@ -137,112 +156,311 @@ Details:
     	work directory that hold output files
 
 To reduce output, use `-debug 0`, e.g., `ffcvt -force -debug 0 -f testf.mp4 ...`
+# Test transcoding single file
 
 == Transcoding: StreamSample.mkv
 ffcvt: to execute -
   ffmpeg -i StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/StreamSample.mkv
 
+Transcoding completed in xxx ms
 Org Size: 0 MB
 New Size: 0 MB
 Saved:    0%
+# Test transcoding different target types
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/StreamSample.mkv
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/StreamSample.mkv
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k /tmp/StreamSample.mkv
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k /tmp/StreamSample.mkv
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -c:v libx264 -x264-params crf=23 -c:a libmp3lame -b:a 256k -q:a 3 /tmp/StreamSample.mp4
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -c:v libx264 -x264-params crf=23 -c:a libmp3lame -b:a 256k -q:a 3 /tmp/StreamSample.mp4
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -c:v libx264 -x264-params crf=33 -c:a aac -b:a 48k -q:a 3 /tmp/StreamSample.m4v
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -c:v libx264 -x264-params crf=33 -c:a aac -b:a 48k -q:a 3 /tmp/StreamSample.m4v
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -c:v libx264 -x264-params crf=20 -pix_fmt yuv420p -c:a libvorbis -q:a 5 /tmp/StreamSample.avi
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -c:v libx264 -x264-params crf=20 -pix_fmt yuv420p -c:a libvorbis -q:a 5 /tmp/StreamSample.avi
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -c:v copy -c:a copy -b:a 64k -c:s copy /tmp/StreamSample.mkv
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -c:v copy -c:a copy -b:a 64k -c:s copy /tmp/StreamSample.mkv
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+# Test adding karaoke audio track
+
+== Transcoding: test1.avi
+] ffmpeg -i test1.avi -filter_complex [0:1]pan=mono|c0=c0-c1[a] -map 0:0 -map [a] -map 0:1 test1_karaoke.mp4
+ffcvt: to execute -
+  ffmpeg -i test1.avi -filter_complex [0:1]pan=mono|c0=c0-c1[a] -map 0:0 -map [a] -map 0:1 test1_karaoke.mp4
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+
+== Transcoding: test1.avi
+] ffmpeg -i test1.avi -filter_complex [0:1]pan=mono|c0=c0-c1[a] -map 0:0 -map [a] -map 0:1 test1_k.avi
+ffcvt: to execute -
+  ffmpeg -i test1.avi -filter_complex [0:1]pan=mono|c0=c0-c1[a] -map 0:0 -map [a] -map 0:1 test1_k.avi
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+# Test transposing different keys
+Transposing from 'F' (349.23) to 'D' (293.66)
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -vn -c:a libmp3lame -b:a 256k -q:a 3 -af atempo=349.23/293.66,asetrate=44100*293.66/349.23 /tmp/StreamSample.mp4
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -vn -c:a libmp3lame -b:a 256k -q:a 3 -af atempo=349.23/293.66,asetrate=44100*293.66/349.23 /tmp/StreamSample.mp4
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+Transposing from 'F#' (369.99) to 'Db' (277.18)
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -vn -c:a libmp3lame -b:a 72k -q:a 3 -af atempo=369.99/277.18,asetrate=44100*277.18/369.99 /tmp/StreamSample.mp4
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -vn -c:a libmp3lame -b:a 72k -q:a 3 -af atempo=369.99/277.18,asetrate=44100*277.18/369.99 /tmp/StreamSample.mp4
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+Transposing from 'Gb' (369.99) to 'A#' (466.16)
+
+== Transcoding: StreamSample.mkv
+] ffmpeg -i StreamSample.mkv -vn -c:a libmp3lame -b:a 72k -af atempo=369.99/466.16,asetrate=44100*466.16/369.99 StreamSample_.mp3
+ffcvt: to execute -
+  ffmpeg -i StreamSample.mkv -vn -c:a libmp3lame -b:a 72k -af atempo=369.99/466.16,asetrate=44100*466.16/369.99 StreamSample_.mp3
+
+Transcoding completed in xxx ms
+Org Size: 0 MB
+New Size: 0 MB
+Saved:    0%
+# Test -sym control
+] None-video file './ffcvt_test.txt' duplicated to dest dir.
 ] Skip symlink file: ./subdir/test_s1.avi
+] None-video file './test-all.sh' duplicated to dest dir.
 ] Skip symlink file: ./test1.avi
 ] Skip symlink file: ./test2.avi
 ] Skip symlink file: ./test3.webm
 
+Transcoding completed in xxx ms
 Org Size: 0 MB
 New Size: 0 MB
 Saved:    0%
+] None-video file './ffcvt_test.txt' duplicated to dest dir.
+] None-video file './test-all.sh' duplicated to dest dir.
 
-== Transcoding [1/4]: 'test_s1.avi'
+== Transcoding [1/4] (36%): 'test_s1.avi'
    under subdir
 ] ffmpeg -i ./subdir/test_s1.avi -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./subdir/test_s1_.mkv
 ffcvt: to execute -
   ffmpeg -i ./subdir/test_s1.avi -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./subdir/test_s1_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 64% in xxx ms
 
-== Transcoding [2/4]: 'test1.avi'
+== Transcoding [2/4] (57%): 'test1.avi'
    under .
 ] ffmpeg -i ./test1.avi -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./test1_.mkv
 ffcvt: to execute -
   ffmpeg -i ./test1.avi -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./test1_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 43% in xxx ms
 
-== Transcoding [3/4]: 'test2.avi'
+== Transcoding [3/4] (78%): 'test2.avi'
    under .
 ] ffmpeg -i ./test2.avi -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./test2_.mkv
 ffcvt: to execute -
   ffmpeg -i ./test2.avi -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./test2_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 22% in xxx ms
 
-== Transcoding [4/4]: 'test3.webm'
+== Transcoding [4/4] (100%): 'test3.webm'
    under .
 ] ffmpeg -i ./test3.webm -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./test3_.mkv
 ffcvt: to execute -
   ffmpeg -i ./test3.webm -c:v libx265 -x265-params crf=28 -c:a libopus -b:a 64k ./test3_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
+Transcoding completed in xxx ms
 Org Size: 0 MB
 New Size: 0 MB
 Saved:    0%
+] None-video file './ffcvt_test.txt' duplicated to dest dir.
 ] Skip symlink file: ./subdir/test_s1.avi
+] None-video file './test-all.sh' duplicated to dest dir.
 ] Skip symlink file: ./test1.avi
 ] Skip symlink file: ./test2.avi
 ] Skip symlink file: ./test3.webm
 
+Transcoding completed in xxx ms
 Org Size: 0 MB
 New Size: 0 MB
 Saved:    0%
+] None-video file './ffcvt_test.txt' duplicated to dest dir.
+] None-video file './test-all.sh' duplicated to dest dir.
 
-== Transcoding [1/4]: 'test_s1.avi'
+== Transcoding [1/4] (36%): 'test_s1.avi'
    under subdir
 ] ffmpeg -i ./subdir/test_s1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./subdir/test_s1_.mkv
 ffcvt: to execute -
   ffmpeg -i ./subdir/test_s1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./subdir/test_s1_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 64% in xxx ms
 
-== Transcoding [2/4]: 'test1.avi'
+== Transcoding [2/4] (57%): 'test1.avi'
    under .
 ] ffmpeg -i ./test1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test1_.mkv
 ffcvt: to execute -
   ffmpeg -i ./test1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test1_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 43% in xxx ms
 
-== Transcoding [3/4]: 'test2.avi'
+== Transcoding [3/4] (78%): 'test2.avi'
    under .
 ] ffmpeg -i ./test2.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test2_.mkv
 ffcvt: to execute -
   ffmpeg -i ./test2.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test2_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 22% in xxx ms
 
-== Transcoding [4/4]: 'test3.webm'
+== Transcoding [4/4] (100%): 'test3.webm'
    under .
 ] ffmpeg -i ./test3.webm -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test3_.mkv
 ffcvt: to execute -
   ffmpeg -i ./test3.webm -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test3_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
+Transcoding completed in xxx ms
 Org Size: 0 MB
 New Size: 0 MB
 Saved:    0%
+] None-video file './ffcvt_test.txt' duplicated to dest dir.
+] None-video file './test-all.sh' duplicated to dest dir.
+
+== Transcoding [1/4] (36%): 'test_s1.avi'
+   under subdir
+] ffmpeg -i ./subdir/test_s1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./subdir/test_s1_.mkv
+ffcvt: to execute -
+  ffmpeg -i ./subdir/test_s1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./subdir/test_s1_.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 64% in xxx ms
+
+== Transcoding [2/4] (57%): 'test1.avi'
+   under .
+] ffmpeg -i ./test1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test1_.mkv
+ffcvt: to execute -
+  ffmpeg -i ./test1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./test1_.mkv
+Time taken so far xxx ms
+] Max conversion count reached.
+] None-video file './ffcvt_test.txt' duplicated to dest dir.
+] None-video file './test-all.sh' duplicated to dest dir.
+
+== Transcoding [1/4] (36%): 'test_s1.avi'
+   under subdir
+] ffmpeg -i ./subdir/test_s1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./subdir/test_s1_.mkv
+ffcvt: to execute -
+  ffmpeg -i ./subdir/test_s1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy ./subdir/test_s1_.mkv
+Time taken so far xxx ms
+] Max conversion count reached.
 ] Transcoding to /tmp/test2
+] None-video file './ffcvt_test.txt' duplicated to dest dir.
+] None-video file './test-all.sh' duplicated to dest dir.
 
-== Transcoding [1/4]: 'test_s1.avi'
+== Transcoding [1/4] (36%): 'test_s1.avi'
    under subdir
 ] ffmpeg -i ./subdir/test_s1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test2/subdir/test_s1.mkv
 ffcvt: to execute -
   ffmpeg -i ./subdir/test_s1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test2/subdir/test_s1.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 64% in xxx ms
 
-== Transcoding [2/4]: 'test1.avi'
+== Transcoding [2/4] (57%): 'test1.avi'
    under .
 ] ffmpeg -i ./test1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test2/test1.mkv
 ffcvt: to execute -
   ffmpeg -i ./test1.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test2/test1.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 43% in xxx ms
 
-== Transcoding [3/4]: 'test2.avi'
+== Transcoding [3/4] (78%): 'test2.avi'
    under .
 ] ffmpeg -i ./test2.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test2/test2.mkv
 ffcvt: to execute -
   ffmpeg -i ./test2.avi -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test2/test2.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 22% in xxx ms
 
-== Transcoding [4/4]: 'test3.webm'
+== Transcoding [4/4] (100%): 'test3.webm'
    under .
 ] ffmpeg -i ./test3.webm -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test2/test3.mkv
 ffcvt: to execute -
   ffmpeg -i ./test3.webm -c:v libvpx-vp9 -b:v 0 -crf 42 -c:a libopus -b:a 64k -c:s copy /tmp/test2/test3.mkv
+Time taken so far xxx ms
+] Taking a breath for: xxx ms
+Finishing the remaining 0% in xxx ms
 
+Transcoding completed in xxx ms
 Org Size: 0 MB
 New Size: 0 MB
 Saved:    0%
diff --git a/video-crf.png b/video-crf.png
deleted file mode 100644
index 50e214f..0000000
Binary files a/video-crf.png and /dev/null differ

More details

Full run details

Historical runs