-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit b801234
Showing
9 changed files
with
969 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
*.egg-info/ | ||
.idea | ||
__pycache__ | ||
build/ | ||
dist/ | ||
venv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2022 Halit Şimşek | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
# Media Fixer | ||
Have you ever had a film or TV show that your TV wasn't able to play its audio? | ||
Well this program is for you. | ||
Media Fixer is a program which converts given media to a playable format. | ||
It's a kind of wrapper for `ffmpeg` that works with multiple files in a concurrent & memory-buffered fashion. | ||
|
||
## Requirements | ||
Python >= 3.8 is required. (CPython and PyPy are both supported)<br> | ||
<br> | ||
Additionally `ffmpeg` and `mediainfo` are required for this program to run. They can be installed on Ubuntu as the following way: | ||
```shell | ||
sudo apt install ffmpeg mediainfo | ||
``` | ||
|
||
## Installation | ||
Media fixer can be either installed directly via pip: | ||
```shell | ||
pip install media-fixer | ||
``` | ||
Or it can be installed from the source: | ||
```shell | ||
git clone https://github.com/simsekhalit/media-fixer.git | ||
python3 -m pip install ./media-fixer | ||
``` | ||
|
||
## Manual | ||
``` | ||
$ python3 -m media_fixer --help | ||
usage: media-fixer [-h] [--audio AUDIO_STREAM] [--input-options INPUT_OPTIONS] [--max-memory MAX_MEMORY] [--output-options OUTPUT_OPTIONS] SOURCE [SOURCE ...] TARGET | ||
A wrapper around ffmpeg to make it work in a concurrent and memory-buffered fashion. | ||
positional arguments: | ||
SOURCE source files/directories (works recursively) | ||
TARGET target directory | ||
optional arguments: | ||
-h, --help show this help message and exit | ||
--audio AUDIO_STREAM select index of audio stream to be used (zero-indexed). defaults to 0 | ||
--input-options INPUT_OPTIONS | ||
specify custom input file options for ffmpeg (overrides default ones) | ||
--max-memory MAX_MEMORY | ||
specify allowed max memory usage as percent | ||
--output-options OUTPUT_OPTIONS | ||
specify custom output file options for ffmpeg (overrides default ones) | ||
For more information: https://github.com/simsekhalit/media-fixer | ||
``` | ||
Media Fixer can be used in one of the two modes, namely Auto and Custom. | ||
|
||
#### Auto Mode | ||
If no output options are given, Media Fixer works in Auto mode. Initially, an audio stream is selected with `--audio`. | ||
For each given file, existing audio codec is detected and if it's in DTS format, it's converted to EAC-3. | ||
Otherwise, it's just copied to the target file as it is. | ||
While doing that only the selected audio stream is processed. Rest of the audio streams are stripped out. | ||
This is useful for saving space since generally there are many audio streams in a film (English, French, Spanish, etc.), | ||
but only one of them is needed. | ||
|
||
#### Custom Mode | ||
If output options are given with `--output-options`, Media Fixer works in custom mode. | ||
All the given files are converted according to given output options. | ||
|
||
## Examples | ||
`SOURCE` and `TARGET` arguments are interpreted in the same way in both modes.<br> | ||
Single or multiple source paths can be given. For any given source path:<br> | ||
If it's a file, its corresponding target is written under the target path.<br> | ||
If it's a directory, then it's traversed recursively and all the files under it are processed. | ||
Source directory tree is generated as the same way in the target path. | ||
Corresponding target files are written accordingly. | ||
|
||
<br> | ||
|
||
> :information_source: `MediaInfo` is a helpful tool for identifying audio streams and their formats. | ||
It can be downloaded from https://mediaarea.net/en/MediaInfo | ||
|
||
<br> | ||
|
||
### 1. There is a film and its 3rd audio (which is the audio stream with index 2) is chosen to be used: | ||
|
||
```shell | ||
python3 -m media_fixer --audio 2 '/mnt/HDD/Matrix 4.mkv' '/mnt/MyUSB' | ||
``` | ||
|
||
`Matrix 4.mkv` is processed and the resulting file is written to `/mnt/USB/Matrix 4.mkv` | ||
|
||
<br> | ||
|
||
### 2. There is a folder which contains a season of a TV show: | ||
|
||
``` | ||
/mnt/HDD/Brooklyn Nine-Nine Season 1 | ||
/mnt/HDD/Brooklyn Nine-Nine Season 1/S01E01.mkv | ||
/mnt/HDD/Brooklyn Nine-Nine Season 1/S01E02.mkv | ||
/mnt/HDD/Brooklyn Nine-Nine Season 1/S01E03.mkv | ||
... | ||
``` | ||
|
||
Following command is executed: | ||
|
||
```shell | ||
python3 -m media_fixer '/mnt/HDD/Brooklyn Nine-Nine Season 1' '/mnt/USB' | ||
``` | ||
|
||
All the episodes are processed and written to `/mnt/USB/Brooklyn Nine-Nine Season 1` | ||
|
||
<br> | ||
|
||
### 3. Custom ffmpeg options wanted to be used: | ||
|
||
```shell | ||
python3 -m media_fixer --output-options '-c:v copy -c:a aac -c:s copy -f matroska -map 0:v -map 0:a:2 -map 0:s' '/mnt/HDD/Brooklyn Nine-Nine Season 1' '/mnt/USB' | ||
``` | ||
Following ffmpeg command is run for each source file: | ||
```shell | ||
ffmpeg -i $source -c:v copy -c:a aac -c:s copy -f matroska -map 0:v -map 0:a:2 -map 0:s -y - | ||
``` | ||
`$source` is replaced with the source file's path and output is captured from stdout, | ||
buffered there and then written to the corresponding target path. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#!/usr/bin/env python3 | ||
from .core import * | ||
|
||
__version__ = "1.0.0" | ||
__version_info__ = (1, 0, 0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/usr/bin/env python3 | ||
import sys | ||
|
||
from .cli import main | ||
|
||
main(sys.argv[1:]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
#!/usr/bin/env python3 | ||
import os | ||
import signal | ||
import sys | ||
from argparse import ArgumentParser, Namespace | ||
from typing import List | ||
|
||
from .core import MediaFixer | ||
|
||
__all__ = [ | ||
"main", | ||
"parse_args", | ||
"setup_signals", | ||
] | ||
|
||
|
||
def setup_signals(media_fixer: MediaFixer) -> None: | ||
def handler(_, __): | ||
media_fixer.terminate() | ||
|
||
for s in (signal.SIGHUP, signal.SIGINT, signal.SIGTERM): | ||
signal.signal(s, handler) | ||
|
||
|
||
def _validate_args(args) -> None: | ||
for path in (*args.sources, args.target): | ||
if not os.path.exists(path): | ||
print(f"'{path}' does not exist!", file=sys.stderr) | ||
sys.exit(2) | ||
|
||
|
||
def parse_args(args: List[str]) -> Namespace: | ||
description = "A wrapper around ffmpeg to make it work in a concurrent and memory-buffered fashion." | ||
epilog = "For more information: https://github.com/simsekhalit/media-fixer" | ||
parser = ArgumentParser("media-fixer", description=description, epilog=epilog) | ||
parser.add_argument("--audio", | ||
help="select index of audio stream to be used (zero-indexed). defaults to 0", | ||
default=0, type=int, dest="audio_stream") | ||
parser.add_argument("--input-options", help="specify custom input file options for ffmpeg (overrides default ones)") | ||
parser.add_argument("--max-memory", type=int, help="specify allowed max memory usage as percent") | ||
parser.add_argument("--output-options", | ||
help="specify custom output file options for ffmpeg (overrides default ones)") | ||
parser.add_argument("sources", nargs="+", help="source files/directories (works recursively)", metavar="SOURCE") | ||
parser.add_argument("target", help="target directory", metavar="TARGET") | ||
args = parser.parse_args(args) | ||
_validate_args(args) | ||
|
||
return args | ||
|
||
|
||
def main(args: List[str]) -> None: | ||
args = parse_args(args) | ||
media_fixer = MediaFixer(**vars(args)) | ||
setup_signals(media_fixer) | ||
media_fixer.run() | ||
|
||
|
||
if __name__ == '__main__': | ||
main(sys.argv[1:]) |
Oops, something went wrong.