clipmind 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clipmind-1.0.0/LICENSE +21 -0
- clipmind-1.0.0/PKG-INFO +136 -0
- clipmind-1.0.0/README.md +74 -0
- clipmind-1.0.0/clipmind/__init__.py +24 -0
- clipmind-1.0.0/clipmind/src/__init__.py +7 -0
- clipmind-1.0.0/clipmind/src/cli/__init__.py +0 -0
- clipmind-1.0.0/clipmind/src/cli/interface.py +73 -0
- clipmind-1.0.0/clipmind/src/core/__init__.py +4 -0
- clipmind-1.0.0/clipmind/src/core/audio_extractor.py +226 -0
- clipmind-1.0.0/clipmind/src/core/validation/__init__.py +2 -0
- clipmind-1.0.0/clipmind/src/core/validation/video_validators.py +73 -0
- clipmind-1.0.0/clipmind/src/core/video_tools.py +672 -0
- clipmind-1.0.0/clipmind/src/utils/__init__.py +4 -0
- clipmind-1.0.0/clipmind/src/utils/ai_utils.py +572 -0
- clipmind-1.0.0/clipmind/src/utils/language.py +10 -0
- clipmind-1.0.0/clipmind/src/utils/resolution.py +7 -0
- clipmind-1.0.0/clipmind/src/utils/validation.py +41 -0
- clipmind-1.0.0/clipmind.egg-info/PKG-INFO +136 -0
- clipmind-1.0.0/clipmind.egg-info/SOURCES.txt +24 -0
- clipmind-1.0.0/clipmind.egg-info/dependency_links.txt +1 -0
- clipmind-1.0.0/clipmind.egg-info/entry_points.txt +2 -0
- clipmind-1.0.0/clipmind.egg-info/requires.txt +47 -0
- clipmind-1.0.0/clipmind.egg-info/top_level.txt +3 -0
- clipmind-1.0.0/pyproject.toml +34 -0
- clipmind-1.0.0/requirements.txt +47 -0
- clipmind-1.0.0/setup.cfg +4 -0
clipmind-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mirmudasir692
|
|
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.
|
clipmind-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clipmind
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A simple and efficient Python tool to extract audio from video files using FFmpeg
|
|
5
|
+
Author-email: Mudasir <your_email@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: video,audio,ffmpeg,converter,media
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: annotated-types==0.7.0
|
|
15
|
+
Requires-Dist: anyio==4.13.0
|
|
16
|
+
Requires-Dist: arabic-reshaper==3.0.0
|
|
17
|
+
Requires-Dist: certifi==2026.2.25
|
|
18
|
+
Requires-Dist: cffi==2.0.0
|
|
19
|
+
Requires-Dist: charset-normalizer==3.4.6
|
|
20
|
+
Requires-Dist: cryptography==46.0.6
|
|
21
|
+
Requires-Dist: distro==1.9.0
|
|
22
|
+
Requires-Dist: dnspython==2.8.0
|
|
23
|
+
Requires-Dist: ffmpeg-python==0.2.0
|
|
24
|
+
Requires-Dist: future==1.0.0
|
|
25
|
+
Requires-Dist: google-ai-generativelanguage==0.6.15
|
|
26
|
+
Requires-Dist: google-api-core==2.30.0
|
|
27
|
+
Requires-Dist: google-api-python-client==2.193.0
|
|
28
|
+
Requires-Dist: google-auth==2.49.1
|
|
29
|
+
Requires-Dist: google-auth-httplib2==0.3.0
|
|
30
|
+
Requires-Dist: google-genai==1.68.0
|
|
31
|
+
Requires-Dist: googleapis-common-protos==1.73.1
|
|
32
|
+
Requires-Dist: grpcio==1.78.0
|
|
33
|
+
Requires-Dist: grpcio-status==1.71.2
|
|
34
|
+
Requires-Dist: h11==0.16.0
|
|
35
|
+
Requires-Dist: httpcore==1.0.9
|
|
36
|
+
Requires-Dist: httplib2==0.31.2
|
|
37
|
+
Requires-Dist: httpx==0.28.1
|
|
38
|
+
Requires-Dist: idna==3.11
|
|
39
|
+
Requires-Dist: numpy==2.4.4
|
|
40
|
+
Requires-Dist: opencv-python==4.13.0.92
|
|
41
|
+
Requires-Dist: proto-plus==1.27.2
|
|
42
|
+
Requires-Dist: protobuf==5.29.6
|
|
43
|
+
Requires-Dist: pyasn1==0.6.3
|
|
44
|
+
Requires-Dist: pyasn1_modules==0.4.2
|
|
45
|
+
Requires-Dist: pycparser==3.0
|
|
46
|
+
Requires-Dist: pydantic==2.12.5
|
|
47
|
+
Requires-Dist: pydantic_core==2.41.5
|
|
48
|
+
Requires-Dist: pymongo==4.16.0
|
|
49
|
+
Requires-Dist: pyparsing==3.3.2
|
|
50
|
+
Requires-Dist: python-bidi==0.6.7
|
|
51
|
+
Requires-Dist: python-dotenv==1.2.2
|
|
52
|
+
Requires-Dist: requests==2.33.0
|
|
53
|
+
Requires-Dist: sniffio==1.3.1
|
|
54
|
+
Requires-Dist: tenacity==9.1.4
|
|
55
|
+
Requires-Dist: tqdm==4.67.3
|
|
56
|
+
Requires-Dist: typing-inspection==0.4.2
|
|
57
|
+
Requires-Dist: typing_extensions==4.15.0
|
|
58
|
+
Requires-Dist: uritemplate==4.2.0
|
|
59
|
+
Requires-Dist: urllib3==2.6.3
|
|
60
|
+
Requires-Dist: websockets==16.0
|
|
61
|
+
Dynamic: license-file
|
|
62
|
+
|
|
63
|
+
# clipmind - Video to Audio Converter
|
|
64
|
+
|
|
65
|
+
A simple and efficient Python tool to extract audio from video files using FFmpeg.
|
|
66
|
+
|
|
67
|
+
## Features
|
|
68
|
+
|
|
69
|
+
- 🎵 Extract high-quality audio (MP3, WAV)
|
|
70
|
+
- ✂️ Video tools: merge, crop, overlay, resolve conversion
|
|
71
|
+
- ✅ File validation and error handling
|
|
72
|
+
- 🖥️ CLI interface and Python library API
|
|
73
|
+
- 🛠️ Minimal dependencies
|
|
74
|
+
|
|
75
|
+
## Installation
|
|
76
|
+
|
|
77
|
+
### Prerequisites
|
|
78
|
+
|
|
79
|
+
1. **Python 3.6+**
|
|
80
|
+
2. **FFmpeg** (must be in system PATH)
|
|
81
|
+
|
|
82
|
+
### Install Dependencies
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install ffmpeg-python
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Usage
|
|
89
|
+
|
|
90
|
+
### Method 1: Command Line Interface
|
|
91
|
+
|
|
92
|
+
Once installed, use the `clipmind` command:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
clipmind -i video.mp4 [-o audio.mp3] [-f mp3]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Examples:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Basic extraction
|
|
102
|
+
clipmind -i video.mp4
|
|
103
|
+
|
|
104
|
+
# Custom output format
|
|
105
|
+
clipmind -i video.mkv -f wav
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Method 2: Python Library
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from clipmind import get_audio_from_video
|
|
112
|
+
|
|
113
|
+
# Extract audio
|
|
114
|
+
success = get_audio_from_video("video.mp4", "audio.mp3")
|
|
115
|
+
|
|
116
|
+
# Advanced video tools
|
|
117
|
+
from clipmind import merge_videos, crop_video
|
|
118
|
+
merge_videos("part1.mp4", "part2.mp4", "merged.mp4")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Project Structure
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
clipmind/
|
|
125
|
+
├── clipmind/ # Core package
|
|
126
|
+
│ └── src/
|
|
127
|
+
│ ├── cli/ # CLI implementation
|
|
128
|
+
│ ├── core/ # Audio & Video tools
|
|
129
|
+
│ └── utils/ # Validations
|
|
130
|
+
├── pyproject.toml # Build configuration
|
|
131
|
+
└── README.md # Documentation
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT
|
clipmind-1.0.0/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# clipmind - Video to Audio Converter
|
|
2
|
+
|
|
3
|
+
A simple and efficient Python tool to extract audio from video files using FFmpeg.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎵 Extract high-quality audio (MP3, WAV)
|
|
8
|
+
- ✂️ Video tools: merge, crop, overlay, resolve conversion
|
|
9
|
+
- ✅ File validation and error handling
|
|
10
|
+
- 🖥️ CLI interface and Python library API
|
|
11
|
+
- 🛠️ Minimal dependencies
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### Prerequisites
|
|
16
|
+
|
|
17
|
+
1. **Python 3.6+**
|
|
18
|
+
2. **FFmpeg** (must be in system PATH)
|
|
19
|
+
|
|
20
|
+
### Install Dependencies
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install ffmpeg-python
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Method 1: Command Line Interface
|
|
29
|
+
|
|
30
|
+
Once installed, use the `clipmind` command:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
clipmind -i video.mp4 [-o audio.mp3] [-f mp3]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Examples:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Basic extraction
|
|
40
|
+
clipmind -i video.mp4
|
|
41
|
+
|
|
42
|
+
# Custom output format
|
|
43
|
+
clipmind -i video.mkv -f wav
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Method 2: Python Library
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from clipmind import get_audio_from_video
|
|
50
|
+
|
|
51
|
+
# Extract audio
|
|
52
|
+
success = get_audio_from_video("video.mp4", "audio.mp3")
|
|
53
|
+
|
|
54
|
+
# Advanced video tools
|
|
55
|
+
from clipmind import merge_videos, crop_video
|
|
56
|
+
merge_videos("part1.mp4", "part2.mp4", "merged.mp4")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Project Structure
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
clipmind/
|
|
63
|
+
├── clipmind/ # Core package
|
|
64
|
+
│ └── src/
|
|
65
|
+
│ ├── cli/ # CLI implementation
|
|
66
|
+
│ ├── core/ # Audio & Video tools
|
|
67
|
+
│ └── utils/ # Validations
|
|
68
|
+
├── pyproject.toml # Build configuration
|
|
69
|
+
└── README.md # Documentation
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Main package file for clipmind."""
|
|
2
|
+
|
|
3
|
+
from .src.core.audio_extractor import get_audio_from_video, extract_audio, get_default_output_path, chunk_video_adaptive
|
|
4
|
+
from .src.core.video_tools import merge_videos, composite_image_over_video, convert_video_resolutions, get_video_thumbnail, detect_video_vulnerability,crop_video, generate_video_summary, generate_subtitle, video_phash
|
|
5
|
+
from .src.utils.validation import validate_video_file, validate_ffmpeg
|
|
6
|
+
|
|
7
|
+
__version__ = "1.0.0"
|
|
8
|
+
__author__ = "clipmind Team"
|
|
9
|
+
__all__ = [
|
|
10
|
+
"get_audio_from_video",
|
|
11
|
+
"extract_audio",
|
|
12
|
+
"get_default_output_path",
|
|
13
|
+
"validate_video_file",
|
|
14
|
+
"validate_ffmpeg",
|
|
15
|
+
"merge_videos",
|
|
16
|
+
"composite_image_over_video",
|
|
17
|
+
"convert_video_resolutions",
|
|
18
|
+
"get_video_thumbnail",
|
|
19
|
+
"crop_video",
|
|
20
|
+
"chunk_video_adaptive",
|
|
21
|
+
"detect_video_vulnerability",
|
|
22
|
+
"generate_video_summary",
|
|
23
|
+
"generate_subtitle","video_phash"
|
|
24
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parse_arguments():
|
|
7
|
+
parser = argparse.ArgumentParser(
|
|
8
|
+
prog="clipmind",
|
|
9
|
+
description="clipmind - Extract audio from video files",
|
|
10
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
11
|
+
epilog="""
|
|
12
|
+
Examples:
|
|
13
|
+
clipmind -i video.mp4 # Extract to video.mp3
|
|
14
|
+
clipmind -i video.mp4 -o audio.wav # Extract to audio.wav
|
|
15
|
+
clipmind -i video.mkv -f mp3 # Extract to video.mp3
|
|
16
|
+
"""
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
parser.add_argument('-i', '--input', required=True, help='Input video file path')
|
|
20
|
+
parser.add_argument('-o', '--output', help='Output audio file path (optional)')
|
|
21
|
+
parser.add_argument('-f', '--format', choices=['mp3', 'wav'], default='mp3',
|
|
22
|
+
help='Output audio format (default: mp3)')
|
|
23
|
+
|
|
24
|
+
return parser.parse_args()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def show_usage_instructions():
|
|
28
|
+
print("clipmind - Audio Extraction Tool")
|
|
29
|
+
print("==============================")
|
|
30
|
+
print("This tool extracts audio from video files using FFmpeg.")
|
|
31
|
+
print()
|
|
32
|
+
print("Usage: clipmind -i <input_video> [-o <output_audio>] [-f <format>]")
|
|
33
|
+
print()
|
|
34
|
+
print("Options:")
|
|
35
|
+
print(" -i, --input Input video file path (required)")
|
|
36
|
+
print(" -o, --output Output audio file path (optional)")
|
|
37
|
+
print(" -f, --format Output format: mp3 or wav (default: mp3)")
|
|
38
|
+
print()
|
|
39
|
+
print("Example: clipmind -i video.mp4 -o audio.mp3")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def validate_and_get_output_path(input_path, output_path=None, audio_format='mp3'):
|
|
43
|
+
if output_path:
|
|
44
|
+
output_path = Path(output_path)
|
|
45
|
+
else:
|
|
46
|
+
input_path = Path(input_path)
|
|
47
|
+
output_filename = input_path.stem + '.' + audio_format
|
|
48
|
+
output_path = input_path.parent / output_filename
|
|
49
|
+
|
|
50
|
+
output_dir = output_path.parent
|
|
51
|
+
if not output_dir.exists():
|
|
52
|
+
print(f"Error: Output directory does not exist: {output_dir}")
|
|
53
|
+
sys.exit(1)
|
|
54
|
+
|
|
55
|
+
return output_path
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main():
|
|
59
|
+
from ..core.audio_extractor import get_audio_from_video
|
|
60
|
+
args = parse_arguments()
|
|
61
|
+
|
|
62
|
+
success = get_audio_from_video(
|
|
63
|
+
args.input,
|
|
64
|
+
args.output,
|
|
65
|
+
args.format
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if success:
|
|
69
|
+
print(f"Successfully extracted audio to: {args.output if args.output else 'default path'}")
|
|
70
|
+
sys.exit(0)
|
|
71
|
+
else:
|
|
72
|
+
print("Error: Audio extraction failed.")
|
|
73
|
+
sys.exit(1)
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
"""Core module for clipmind package."""
|
|
2
|
+
from .audio_extractor import get_audio_from_video, extract_audio, get_default_output_path
|
|
3
|
+
from .video_tools import merge_videos, composite_image_over_video, convert_video_resolutions, get_video_thumbnail, crop_video, video_phash
|
|
4
|
+
__all__ = ["get_audio_from_video", "extract_audio", "get_default_output_path", "merge_videos", "composite_image_over_video", "convert_video_resolutions", "get_video_thumbnail", "crop_video", "video_phash"]
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import ffmpeg
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from ..utils.validation import validate_video_file, validate_ffmpeg
|
|
6
|
+
from ..cli.interface import validate_and_get_output_path
|
|
7
|
+
from ..utils.resolution import RESOLUTION_PROFILES
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def extract_audio(video_path, output_path, audio_format='mp3', start=None, end=None):
|
|
11
|
+
try:
|
|
12
|
+
audio_codec = 'mp3' if audio_format.lower() == 'mp3' else 'pcm_s16le'
|
|
13
|
+
|
|
14
|
+
input_kwargs = {}
|
|
15
|
+
if start is not None:
|
|
16
|
+
input_kwargs['ss'] = start
|
|
17
|
+
if end is not None:
|
|
18
|
+
input_kwargs['to'] = end
|
|
19
|
+
|
|
20
|
+
stream = ffmpeg.input(str(video_path), **input_kwargs)
|
|
21
|
+
stream = ffmpeg.output(stream, str(output_path), acodec=audio_codec, loglevel='quiet')
|
|
22
|
+
|
|
23
|
+
ffmpeg.run(stream, overwrite_output=True, quiet=True)
|
|
24
|
+
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
except ImportError:
|
|
28
|
+
return False
|
|
29
|
+
except ffmpeg.Error as e:
|
|
30
|
+
return False
|
|
31
|
+
except Exception as e:
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
def get_default_output_path(video_path, audio_format='mp3'):
|
|
35
|
+
video_path = Path(video_path)
|
|
36
|
+
output_filename = video_path.stem + '.' + audio_format
|
|
37
|
+
return video_path.parent / output_filename
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_audio_from_video(video_path, output_path=None, audio_format='mp3', start=None, end=None):
|
|
41
|
+
"""
|
|
42
|
+
Library function to extract audio from video.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
video_path (str): Path to the input video file
|
|
46
|
+
output_path (str, optional): Path for the output audio file
|
|
47
|
+
audio_format (str): Audio format ('mp3' or 'wav')
|
|
48
|
+
start (float/str, optional): Start time of the slice (e.g., 10.5 or "00:00:10")
|
|
49
|
+
end (float/str, optional): End time of the slice (e.g., 15.0 or "00:00:15")
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
bool: True if extraction was successful, False otherwise
|
|
53
|
+
"""
|
|
54
|
+
if not validate_video_file(video_path):
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
if not validate_ffmpeg():
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
output_path = validate_and_get_output_path(video_path, output_path, audio_format)
|
|
61
|
+
|
|
62
|
+
return extract_audio(video_path, str(output_path), audio_format, start, end)
|
|
63
|
+
|
|
64
|
+
def chunk_video_adaptive(video_path, output_dir=None, resolutions=None, segment_duration=10):
|
|
65
|
+
"""
|
|
66
|
+
Break video into HLS chunks at multiple resolutions with master manifest.
|
|
67
|
+
Works with both local paths and URLs (http/https).
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
video_path (str): Path or URL to input video
|
|
71
|
+
output_dir (str, optional): Directory for output chunks. Default: ./video_chunks/
|
|
72
|
+
resolutions (list): List of resolution keys (e.g., ['360p', '720p']). Default: ['360p', '720p', '1080p']
|
|
73
|
+
segment_duration (int): Target duration of each segment in seconds. Default: 10
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
dict or False: On success returns {
|
|
77
|
+
'master_manifest': Path, # Path to master.m3u8
|
|
78
|
+
'output_dir': Path, # Root output directory
|
|
79
|
+
'variants': { # Dict of generated variants
|
|
80
|
+
'360p': {
|
|
81
|
+
'manifest': Path, # Path to variant playlist
|
|
82
|
+
'segments_dir': Path, # Directory containing .ts files
|
|
83
|
+
'segment_count': int # Number of segments generated
|
|
84
|
+
},
|
|
85
|
+
...
|
|
86
|
+
}
|
|
87
|
+
}, returns False on failure
|
|
88
|
+
"""
|
|
89
|
+
try:
|
|
90
|
+
if not validate_ffmpeg():
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
# Setup paths
|
|
94
|
+
video_path_str = str(video_path)
|
|
95
|
+
if output_dir is None:
|
|
96
|
+
# Extract filename from URL or path for default naming
|
|
97
|
+
base_name = Path(video_path_str.split('?')[0]).stem or 'video'
|
|
98
|
+
output_dir = Path(f"{base_name}_chunks")
|
|
99
|
+
else:
|
|
100
|
+
output_dir = Path(output_dir)
|
|
101
|
+
|
|
102
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
|
|
104
|
+
# Default resolutions if not specified
|
|
105
|
+
if resolutions is None:
|
|
106
|
+
resolutions = ['360p', '720p', '1080p']
|
|
107
|
+
|
|
108
|
+
# Validate resolutions
|
|
109
|
+
for res in resolutions:
|
|
110
|
+
if res not in RESOLUTION_PROFILES:
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
variants = {}
|
|
114
|
+
|
|
115
|
+
# Generate chunks for each resolution
|
|
116
|
+
for res_key in resolutions:
|
|
117
|
+
profile = RESOLUTION_PROFILES[res_key]
|
|
118
|
+
|
|
119
|
+
# Create subdirectory for this resolution
|
|
120
|
+
res_dir = output_dir / res_key
|
|
121
|
+
res_dir.mkdir(exist_ok=True)
|
|
122
|
+
|
|
123
|
+
manifest_path = res_dir / "playlist.m3u8"
|
|
124
|
+
segment_pattern = str(res_dir / "segment_%03d.ts")
|
|
125
|
+
|
|
126
|
+
# Build ffmpeg stream
|
|
127
|
+
stream = ffmpeg.input(video_path_str)
|
|
128
|
+
|
|
129
|
+
# Apply scaling and encoding settings
|
|
130
|
+
stream = ffmpeg.filter(stream, 'scale', profile['width'], profile['height'])
|
|
131
|
+
|
|
132
|
+
# HLS output settings
|
|
133
|
+
stream = ffmpeg.output(
|
|
134
|
+
stream,
|
|
135
|
+
str(manifest_path),
|
|
136
|
+
format='hls',
|
|
137
|
+
start_number=0,
|
|
138
|
+
hls_time=segment_duration,
|
|
139
|
+
hls_playlist_type='vod',
|
|
140
|
+
hls_segment_filename=segment_pattern,
|
|
141
|
+
hls_base_url=f"{res_key}/", # Relative path for client resolution
|
|
142
|
+
vcodec='h264',
|
|
143
|
+
acodec='aac',
|
|
144
|
+
video_bitrate=profile['video_bitrate'],
|
|
145
|
+
audio_bitrate=profile['audio_bitrate'],
|
|
146
|
+
preset='fast',
|
|
147
|
+
pix_fmt='yuv420p',
|
|
148
|
+
loglevel='quiet'
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
ffmpeg.run(stream, overwrite_output=True, quiet=True)
|
|
152
|
+
|
|
153
|
+
# Count generated segments
|
|
154
|
+
segments = list(res_dir.glob("segment_*.ts"))
|
|
155
|
+
|
|
156
|
+
variants[res_key] = {
|
|
157
|
+
'manifest': manifest_path,
|
|
158
|
+
'segments_dir': res_dir,
|
|
159
|
+
'segment_count': len(segments)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# Generate master playlist
|
|
163
|
+
master_manifest = output_dir / "master.m3u8"
|
|
164
|
+
_write_master_playlist(master_manifest, resolutions, variants)
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
'master_manifest': master_manifest,
|
|
168
|
+
'output_dir': output_dir,
|
|
169
|
+
'variants': variants
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
except ImportError:
|
|
173
|
+
return False
|
|
174
|
+
except ffmpeg.Error as e:
|
|
175
|
+
return False
|
|
176
|
+
except Exception as e:
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _write_master_playlist(master_path, resolutions, variants):
|
|
181
|
+
"""Generate master HLS playlist referencing all variant playlists."""
|
|
182
|
+
lines = ["#EXTM3U"]
|
|
183
|
+
|
|
184
|
+
for res_key in resolutions:
|
|
185
|
+
profile = RESOLUTION_PROFILES[res_key]
|
|
186
|
+
# Relative path to variant playlist (e.g., "360p/playlist.m3u8")
|
|
187
|
+
variant_path = f"{res_key}/playlist.m3u8"
|
|
188
|
+
|
|
189
|
+
lines.append(
|
|
190
|
+
f"#EXT-X-STREAM-INF:"
|
|
191
|
+
f"BANDWIDTH={profile['bandwidth']},"
|
|
192
|
+
f"RESOLUTION={profile['width']}x{profile['height']},"
|
|
193
|
+
f"NAME=\"{res_key}\""
|
|
194
|
+
)
|
|
195
|
+
lines.append(variant_path)
|
|
196
|
+
|
|
197
|
+
master_path.write_text('\n'.join(lines) + '\n')
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def chunk_video_single(video_path, output_dir=None, resolution='720p', segment_duration=10):
|
|
201
|
+
"""
|
|
202
|
+
Generate HLS chunks for single resolution only.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
video_path (str): Path or URL to input video
|
|
206
|
+
output_dir (str, optional): Output directory
|
|
207
|
+
resolution (str): Single resolution (e.g., '720p')
|
|
208
|
+
segment_duration (int): Seconds per segment
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
dict or False: {'manifest': Path, 'segments_dir': Path, 'output_dir': Path} or False
|
|
212
|
+
"""
|
|
213
|
+
result = chunk_video_adaptive(
|
|
214
|
+
video_path=video_path,
|
|
215
|
+
output_dir=output_dir,
|
|
216
|
+
resolutions=[resolution],
|
|
217
|
+
segment_duration=segment_duration
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if result:
|
|
221
|
+
return {
|
|
222
|
+
'manifest': result['variants'][resolution]['manifest'],
|
|
223
|
+
'segments_dir': result['variants'][resolution]['segments_dir'],
|
|
224
|
+
'output_dir': result['output_dir']
|
|
225
|
+
}
|
|
226
|
+
return False
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
def validate_video(video_path: str = ""):
|
|
6
|
+
"""
|
|
7
|
+
Validates a video file for corruption and malicious structure.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
video_path (str): Path to the video file.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
tuple: (bool, str) -> (is_valid, reason)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
if video_path == "":
|
|
17
|
+
raise ValueError("video_path must be provided")
|
|
18
|
+
|
|
19
|
+
if not os.path.exists(video_path):
|
|
20
|
+
return False, "File does not exist."
|
|
21
|
+
|
|
22
|
+
if os.path.getsize(video_path) == 0:
|
|
23
|
+
return False, "File is empty (corrupt)."
|
|
24
|
+
cmd = [
|
|
25
|
+
'ffprobe',
|
|
26
|
+
'-v', 'error',
|
|
27
|
+
'-show_streams',
|
|
28
|
+
'-select_streams', 'v',
|
|
29
|
+
'-of', 'json',
|
|
30
|
+
video_path
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
result = subprocess.run(
|
|
35
|
+
cmd,
|
|
36
|
+
stdout=subprocess.PIPE,
|
|
37
|
+
stderr=subprocess.PIPE,
|
|
38
|
+
text=True,
|
|
39
|
+
timeout=10
|
|
40
|
+
)
|
|
41
|
+
if result.returncode != 0:
|
|
42
|
+
error_msg = result.stderr.strip()
|
|
43
|
+
if not error_msg:
|
|
44
|
+
error_msg = "Unknown parsing error."
|
|
45
|
+
return False, f"File is corrupted or invalid format: {error_msg}"
|
|
46
|
+
try:
|
|
47
|
+
data = json.loads(result.stdout)
|
|
48
|
+
streams = data.get('streams', [])
|
|
49
|
+
|
|
50
|
+
if not streams:
|
|
51
|
+
return False, "Malicious/Invalid: No video stream found. File might be renamed or mislabeled."
|
|
52
|
+
|
|
53
|
+
video_stream = streams[0]
|
|
54
|
+
codec_name = video_stream.get('codec_name', '')
|
|
55
|
+
width = video_stream.get('width', 0)
|
|
56
|
+
|
|
57
|
+
if not codec_name:
|
|
58
|
+
return False, "Invalid video stream detected."
|
|
59
|
+
|
|
60
|
+
if width == 0:
|
|
61
|
+
return False, "Corrupted video metadata (invalid resolution)."
|
|
62
|
+
|
|
63
|
+
except json.JSONDecodeError:
|
|
64
|
+
return False, "Malicious/Corrupt: File structure is unreadable."
|
|
65
|
+
if result.stderr and "Invalid data" in result.stderr:
|
|
66
|
+
return False, f"Warning: File contains corrupted segments. {result.stderr[:100]}"
|
|
67
|
+
|
|
68
|
+
return True, "Video file is valid."
|
|
69
|
+
|
|
70
|
+
except subprocess.TimeoutExpired:
|
|
71
|
+
return False, "Validation timed out. File might be malformed or infinite."
|
|
72
|
+
except Exception as e:
|
|
73
|
+
return False, f"An unexpected error occurred: {str(e)}"
|