├── .gitignore ├── LICENSE.md ├── README.md ├── audio_extract ├── __init__.py ├── execute.py ├── ffmpeg.py ├── utils.py └── validators.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .idea/* 3 | venv 4 | dist 5 | build 6 | audio_extract.egg-info 7 | .vscode 8 | test.py 9 | **.mp4 10 | **.mp3 -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Riadh Azzoun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Audio Extract 2 | 3 | audio-extract is a Python library that allows you to extract audio from video files and trim the audio according to your 4 | needs 5 | 6 | ## Description 7 | 8 | audio-extract is a Python library that allows you to extract audio from video files and trim the audio according to your 9 | needs. You can use it to create audio clips from movies, podcasts, or any other video source. It supports various audio 10 | and video formats, such as MP3, WAV, OGG, MP4, AVI, and MKV. 11 | 12 | ## Installing 13 | 14 | - Install from Pypi: 15 | 16 | ```bash 17 | pip install audio-extract 18 | ``` 19 | 20 | - Install from GitHub: 21 | 22 | ```bash 23 | pip install git+https://github.com/riad-azz/audio-extract.git 24 | ``` 25 | 26 | ## Getting Started 27 | 28 | ### AudioExtract - Info 29 | 30 | The application is pretty straightforward all you need is to import the `extract_audio` function. The function args: 31 | 32 | * **`input_path`** : The path to the input (Video/Audio) file. 33 | 34 | * **`output_path`**: The path to the extracted audio file. The default value is `./audio.mp3`. 35 | 36 | * **`output_format`**: The format of the extracted audio. The default value is `mp3`. 37 | 38 | * **`start_time`**: The start time of the output in `HH:MM:SS` or `MM:SS` format. The default value is `00:00:00`. 39 | 40 | * **`duration`**: The duration of the extracted audio in seconds _(float)_. The default value is `None` which means 41 | full audio will be extracted if `start_time` is set to `00:00:00`. 42 | 43 | * **`overwrite`**: Whether to overwrite the output file if it already exists or not. The default value is `False`. 44 | 45 | The supported file formats: 46 | 47 | - Supported audio formats : `WAV, OGG, MP3, AAC, FLAC, M4A, OGA, OPUS` 48 | 49 | - Supported video formats : `MP4, MKV, WEBM, FLV, AVI, MOV, WMV, M4V` 50 | 51 | ### Executing the program 52 | 53 | #### Extract full audio: 54 | 55 | ```python 56 | from audio_extract import extract_audio 57 | 58 | extract_audio(input_path="./video.mp4", output_path="./audio.mp3") 59 | ``` 60 | 61 | This will create a `mp3` file called `audio.mp3` that contains the full audio of the video file `video.mp4`. 62 | 63 | #### Extract sub clip audio: 64 | 65 | ```python 66 | from audio_extract import extract_audio 67 | 68 | extract_audio(input_path="./video.mp4", 69 | output_path="./audio.mp3", 70 | start_time="00:30", 71 | overwrite=True) 72 | ``` 73 | 74 | This will create a `mp3` file called `audio.mp3` that starts after the first 30 seconds of the video file `video.mp4` 75 | and will overwrite `audio.mp3` file if it already exists. 76 | 77 | #### Extract sub clip audio with custom duration 78 | 79 | ```python 80 | from audio_extract import extract_audio 81 | 82 | extract_audio(input_path="./video.mp4", 83 | output_path="./audio.mp3", 84 | start_time="00:25", 85 | duration=15.0) 86 | ``` 87 | 88 | This will convert video file `video.mp4` to a mp3 file starting from `00:25` to `00:40` 89 | called `audio.mp3` that will have a duration of `00:15`. 90 | 91 | #### Trim audio: 92 | 93 | ```python 94 | from audio_extract import extract_audio 95 | 96 | extract_audio(input_path="./audio.mp3", 97 | output_path="./new_audio.mp3", 98 | start_time="00:05", 99 | duration=20.0) 100 | ``` 101 | 102 | This will trim the `audio.mp3` file starting from `00:05` to `00:25` to a `mp3` file called `new_audio.mp3` that will 103 | have a duration of `00:20`. 104 | 105 | ## Running Command-Line-Interface 106 | 107 | ### CLI Arguments 108 | 109 | The following cli arguments are supported: 110 | 111 | * **`--input`** or **`-i`** : The path to the input (Video/Audio) file. 112 | 113 | * **`--output`** or **`-o`** : The path to the extracted audio file. The default value is `./audio.mp3`. 114 | 115 | * **`--format`** or **`-f`** : The format of the extracted audio. The default value is `mp3`. 116 | 117 | * **`--start-time`** or **`-st`** : The start time of the output in `HH:MM:SS` or `MM:SS` format. The default value 118 | is `00:00:00`. 119 | 120 | * **`--duration`** or **`-d`** : The duration of the extracted audio in seconds _(float)_, The default value is `None` 121 | which means full audio will be extracted if `start_time` is set to `00:00:00`. 122 | 123 | * **`--overwrite`** or **`-ow`** : Whether to overwrite the output file if it already exists or not. The default value 124 | is `False`. 125 | 126 | ### CLI Usage Example: 127 | 128 | Here is an example of using the CLI to extract audio: 129 | 130 | ```bash 131 | audio-extract --input="./video.mp4" --output="./audios/extracted_audio.wav" --format="wav" 132 | ``` 133 | 134 | This command will extract the full audio starting from `video.mp4` to a `wav` file called `extracted_audio.wav` and will 135 | be saved to the folder `./audios/`. The folder will be automatically created if it doesn't exist. 136 | 137 | ## Authors 138 | 139 | Riadh Azzoun - [@riad-azz](https://github.com/riad-azz) 140 | 141 | ## License 142 | 143 | This project is licensed under the [MIT] License - see the LICENSE.md file for details 144 | -------------------------------------------------------------------------------- /audio_extract/__init__.py: -------------------------------------------------------------------------------- 1 | from audio_extract.ffmpeg import extract_audio 2 | 3 | __all__ = [extract_audio, ] 4 | -------------------------------------------------------------------------------- /audio_extract/execute.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | from audio_extract import extract_audio 5 | 6 | 7 | def main(): 8 | # Create the parser 9 | parser = argparse.ArgumentParser() 10 | # Add the arguments 11 | parser.add_argument('--input', '-i', 12 | help='The path to the video/audio input file.', 13 | required=True, type=str) 14 | parser.add_argument('--output', '-o', 15 | help='The path to the extracted audio file.', 16 | default='./audio.mp3', type=str) 17 | parser.add_argument('--format', '-f', 18 | help='The format of the extracted audio file. ' 19 | 'Only (wav, ogg, mp3, aac, flac, m4a, oga, opus) are supported', 20 | default='mp3', type=str) 21 | parser.add_argument('--start-time', '-st', 22 | help='The start time of the extracted audio \"HH:MM:SS\" or \"MM:SS\" format.', 23 | default='00:00:00', type=str) 24 | parser.add_argument('--duration', '-d', 25 | help='The duration of the extracted audio in seconds.', type=float, default=None) 26 | parser.add_argument('--overwrite', '-ow', help='Overwrite the output file if it exists.', type=bool, default=False) 27 | # Parse the arguments 28 | args = parser.parse_args() 29 | input_path = args.input 30 | output_path = args.output 31 | output_format = args.format 32 | start_time = args.start_time 33 | duration = args.duration 34 | overwrite = args.overwrite 35 | 36 | extract_audio(input_path=input_path, output_path=output_path, output_format=output_format, start_time=start_time, 37 | duration=duration, 38 | overwrite=overwrite) 39 | 40 | 41 | if __name__ == '__main__': 42 | main() 43 | -------------------------------------------------------------------------------- /audio_extract/ffmpeg.py: -------------------------------------------------------------------------------- 1 | # Other modules 2 | import subprocess 3 | import imageio_ffmpeg 4 | 5 | # Local modules 6 | from audio_extract.validators import AudioExtractValidator 7 | 8 | FFMPEG_BINARY = imageio_ffmpeg.get_ffmpeg_exe() 9 | 10 | 11 | def extract_audio(input_path: str, output_path: str = "./audio.mp3", output_format: str = "mp3", 12 | start_time: str = "00:00:00", 13 | duration: float = None, 14 | overwrite: bool = False): 15 | validator = AudioExtractValidator(input_path, output_path, output_format, duration, start_time, overwrite) 16 | result = validator.validate() 17 | 18 | cleaned_input_path = result["input_path"] 19 | cleaned_output_path = result["output_path"] 20 | cleaned_output_format = result["output_format"] 21 | cleaned_start_time = result["start_time"] 22 | cleaned_duration = result["duration"] 23 | 24 | command = [FFMPEG_BINARY, 25 | '-i', cleaned_input_path, 26 | '-ss', cleaned_start_time, 27 | '-f', cleaned_output_format, 28 | '-y', cleaned_output_path] 29 | 30 | if cleaned_duration: 31 | command.insert(3, "-t") 32 | command.insert(4, cleaned_duration) 33 | 34 | result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 35 | if result.returncode == 0: 36 | print(f"Success : audio file has been saved to \"{cleaned_output_path}\".") 37 | else: 38 | error = result.stderr.decode().strip().split("\n")[-1] 39 | raise Exception(f"Failed : {error}.") 40 | -------------------------------------------------------------------------------- /audio_extract/utils.py: -------------------------------------------------------------------------------- 1 | import mutagen 2 | 3 | 4 | def media_duration(file_path: str): 5 | file = mutagen.File(file_path) 6 | duration = file.info.length 7 | return duration 8 | 9 | 10 | def seconds_to_hms(seconds: float | int) -> str: 11 | hours = int(seconds // 3600) 12 | minutes = int((seconds % 3600) // 60) 13 | seconds = int(seconds % 60) 14 | hms = "{:02d}:{:02d}:{:02d}".format(hours, minutes, seconds) 15 | return hms 16 | 17 | 18 | def hms_to_seconds(time_str: str) -> float: 19 | # Split the time string into hours, minutes, and seconds 20 | time_parts = time_str.split(':') 21 | if len(time_parts) == 3: # HH:MM:SS format 22 | hours, minutes, seconds = map(int, time_parts) 23 | elif len(time_parts) == 2: # MM:SS format 24 | hours = 0 25 | minutes, seconds = map(int, time_parts) 26 | else: 27 | raise Exception("Invalid time format. Must be in HH:MM:SS or MM:SS format.") 28 | 29 | # Calculate the total number of seconds 30 | total_seconds = hours * 3600 + minutes * 60 + seconds 31 | 32 | # Return the total number of seconds as a float 33 | return float(total_seconds) 34 | -------------------------------------------------------------------------------- /audio_extract/validators.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | from audio_extract import utils 4 | 5 | SUPPORTED_AUDIO_FORMATS = [ 6 | 'wav', 'ogg', 'mp3', 'aac', 'flac', 'm4a', 'oga', 'opus' 7 | ] 8 | 9 | SUPPORTED_VIDEO_FORMATS = [ 10 | 'mp4', 'mkv', 'webm', 'flv', 'avi', 'mov', 'wmv', 'm4v' 11 | ] 12 | 13 | SUPPORTED_FFMPEG_FORMATS = SUPPORTED_AUDIO_FORMATS + SUPPORTED_VIDEO_FORMATS 14 | 15 | 16 | class AudioExtractValidator: 17 | 18 | def __init__(self, input_path: str, output_path: str, output_format: str, duration: float, start_time: str, 19 | overwrite: bool): 20 | self.input_path = input_path 21 | self.output_path = output_path 22 | self.output_format = output_format 23 | self.duration = duration 24 | self.start_time = start_time 25 | self.overwrite = overwrite 26 | 27 | def validate(self) -> dict: 28 | # Validate and clean all inputs 29 | self._validate_input_path() 30 | self._validate_output_format() 31 | self._validate_output_path() 32 | self._validate_start_time() 33 | self._validate_duration() 34 | # Return cleaned and validated inputs 35 | return self.__dict__ 36 | 37 | def _validate_input_path(self): 38 | input_path = os.path.abspath(self.input_path) 39 | 40 | if not any(input_path.endswith("." + x) for x in SUPPORTED_FFMPEG_FORMATS): 41 | raise Exception(f"Input file format not supported. Only (Video/Audio) allowed.") 42 | 43 | if not os.path.isfile(input_path): 44 | raise Exception(f"{input_path} was not found, please provide a valid file path.") 45 | 46 | self.input_path = input_path 47 | 48 | def _validate_output_path(self): 49 | output_path = self.output_path 50 | if output_path.endswith("/") or output_path.endswith("\\"): 51 | output_path += "audio." + self.output_format 52 | 53 | output_path = os.path.abspath(output_path) 54 | output_parts = output_path.split(os.sep) 55 | filename = output_parts[-1] 56 | folder_path = f"{os.sep}".join(output_parts[:-1]) 57 | 58 | if not os.path.exists(folder_path): 59 | os.makedirs(folder_path) 60 | 61 | extension = "." + self.output_format 62 | if not filename.endswith(extension): 63 | output_path = output_path + extension 64 | 65 | if os.path.isfile(output_path) and not self.overwrite: 66 | raise Exception(f"File already exists in output path: {output_path}.") 67 | 68 | self.output_path = output_path 69 | 70 | def _validate_output_format(self): 71 | output_format = self.output_format 72 | if output_format not in SUPPORTED_AUDIO_FORMATS: 73 | supported_formats = ', '.join(SUPPORTED_AUDIO_FORMATS) 74 | raise Exception(f"Output format {self.output_format} not supported, Only ({supported_formats}) are supported") 75 | 76 | def _validate_start_time(self): 77 | start_time = self.start_time 78 | 79 | if start_time == "00:00:00": 80 | return 81 | 82 | pattern = r"^(?:(\d{1,2}):)?(\d{1,2}):(\d{1,2})$" 83 | if not re.match(pattern, start_time): 84 | raise Exception("Invalid time format. Must be in HH:MM:SS or MM:SS format.") 85 | 86 | file_duration = utils.media_duration(self.input_path) 87 | start_time_seconds = utils.hms_to_seconds(self.start_time) 88 | if start_time_seconds > file_duration: 89 | raise Exception("Start time can't be longer than the input (Video/Audio) file duration") 90 | 91 | self.start_time = start_time 92 | 93 | def _validate_duration(self): 94 | if not self.duration: 95 | return 96 | 97 | duration = self.duration 98 | if duration == 0: 99 | raise Exception("Duration can't be 0") 100 | elif duration < 0: 101 | raise Exception("Duration can't be negative") 102 | 103 | file_duration = utils.media_duration(self.input_path) 104 | start_time_seconds = utils.hms_to_seconds(self.start_time) 105 | time_sum = duration + start_time_seconds 106 | if time_sum > file_duration: 107 | raise Exception("Duration can't be longer than the input (Video/Audio) file duration") 108 | 109 | self.duration = str(duration) 110 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import find_packages, setup, Command 3 | except ImportError: 4 | raise ImportError( 5 | "Audio Extract could not be installed, probably because " 6 | "setuptools is not installed on this computer.\n" 7 | "Install setuptools with $ pip install setuptools" 8 | "try again." 9 | ) 10 | 11 | 12 | class AddToPathCommand(Command): 13 | """ 14 | Custom command to add package to system's PATH during installation. 15 | """ 16 | description = 'Add package to system PATH' 17 | user_options = [] 18 | 19 | def initialize_options(self): 20 | pass 21 | 22 | def finalize_options(self): 23 | pass 24 | 25 | def run(self): 26 | # Add package directory to PATH 27 | import sys 28 | from pathlib import Path 29 | 30 | package_dir = Path(__file__).resolve().parent 31 | if str(package_dir) not in sys.path: 32 | sys.path.insert(0, str(package_dir)) 33 | 34 | 35 | requires = [ 36 | "ffmpeg-python==0.2.0", 37 | "imageio-ffmpeg==0.4.8", 38 | "mutagen==1.46.0" 39 | ] 40 | 41 | long_description = open('README.md').read() 42 | long_description_content_type = 'text/markdown' 43 | 44 | setup( 45 | name='audio_extract', 46 | version='0.7.0', 47 | author='riad-azz', 48 | author_email='riadh.azzoun@hotmail.com', 49 | description='Extract and trim audio from videos or trim audios.', 50 | long_description=long_description, 51 | long_description_content_type=long_description_content_type, 52 | url='https://github.com/riad-azz/audio-extract', 53 | project_urls={ 54 | "Source": "https://github.com/riad-azz/audio-extract", 55 | }, 56 | packages=find_packages(exclude=["docs", "tests"]), 57 | install_requires=requires, 58 | license="MIT License", 59 | keywords=["convert video", "audio", "ffmpeg", "video to mp3"], 60 | entry_points={ 61 | 'console_scripts': [ 62 | 'audio-extract=audio_extract.execute:main', 63 | ], 64 | }, 65 | cmdclass={ 66 | 'add_to_path': AddToPathCommand, 67 | }, 68 | classifiers=[ 69 | "Development Status :: 5 - Production/Stable", 70 | "Intended Audience :: Developers", 71 | "Natural Language :: English", 72 | "Programming Language :: Python :: 3.11", 73 | "License :: OSI Approved :: MIT License", 74 | "Operating System :: OS Independent", 75 | "Topic :: Multimedia", 76 | "Topic :: Multimedia :: Sound/Audio", 77 | "Topic :: Multimedia :: Video", 78 | "Topic :: Multimedia :: Video :: Conversion", 79 | ], 80 | ) 81 | --------------------------------------------------------------------------------