Nipun Claude commited on
Commit
1081399
·
1 Parent(s): 50afa2a

Add Hugging Face Space support with streamlined README

Browse files

- Rename video_trimmer_demo.py → app.py for HF Spaces
- Add HF metadata to README.md for Space deployment
- Separate documentation: README_DOCS.md for GitHub Pages
- Update GitHub Actions to use README_DOCS.md for docs site
- Maintain both GitHub repo and HF Space deployment options

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (5) hide show
  1. .github/workflows/pages.yml +1 -1
  2. README.md +44 -257
  3. README_DOCS.md +284 -0
  4. README_HF.md +33 -0
  5. app.py +695 -0
.github/workflows/pages.yml CHANGED
@@ -21,7 +21,7 @@ jobs:
21
 
22
  - name: Copy README and assets to docs
23
  run: |
24
- cp README.md docs/README.md
25
  cp -r demo docs/
26
 
27
  - name: Setup Pages
 
21
 
22
  - name: Copy README and assets to docs
23
  run: |
24
+ cp README_DOCS.md docs/index.md
25
  cp -r demo docs/
26
 
27
  - name: Setup Pages
README.md CHANGED
@@ -1,284 +1,71 @@
1
- A fast and efficient video trimming toolkit with both a **web interface** and **command-line tool** for MP4 video processing. Features visual trimming with drag-to-scrub sliders and automatic audio extraction.
2
-
3
- **Documentation**: https://nipunbatra.github.io/video-toolkit/
4
-
5
- ## Features
6
-
7
- - **Web Interface**: Interactive Gradio demo with drag-to-trim sliders
8
- - **Google Drive Integration**: Load videos from and upload results to Google Drive
9
- - **Command Line**: Fast bash script for automated processing
10
- - **Smart Trimming**: Visual video scrubbing to find exact cut points
11
- - **Audio Extraction**: Automatic AAC extraction with built-in player
12
- - **Minimal Processing**: Stream copying when possible for speed
13
- - **Cross-Platform**: Works on macOS, Linux, and Windows WSL
14
-
15
- ## Prerequisites
16
 
17
- - **ffmpeg** (version 4.0 or higher) - The core dependency for video processing
18
- - **Bash shell** - Available on macOS, Linux, and Windows WSL
19
 
20
- ### Installing ffmpeg
21
 
22
- | Platform | Command |
23
- |----------|---------|
24
- | macOS | `brew install ffmpeg` |
25
- | Ubuntu/Debian | `sudo apt update && sudo apt install ffmpeg` |
26
- | Fedora/CentOS | `sudo dnf install ffmpeg` |
27
- | Windows | Download from [ffmpeg.org](https://ffmpeg.org/download.html) or use `winget install FFmpeg` |
28
 
29
- ## Installation
30
 
31
- ### Quick Install (Recommended)
32
 
33
- **Step 1:** Download and make executable in one step
34
- ```bash
35
- curl -O https://raw.githubusercontent.com/nipunbatra/video-toolkit/main/trim-convert.sh && chmod +x trim-convert.sh
36
- ```
37
 
38
- **Step 2:** Verify the script works
39
- ```bash
40
- ./trim-convert.sh --help
41
- ```
42
-
43
- ### Manual Install
44
-
45
- **Step 1:** Clone this repository
46
  ```bash
47
  git clone https://github.com/nipunbatra/video-toolkit.git
48
  cd video-toolkit
49
- ```
50
-
51
- **Step 2:** Make the script executable
52
- ```bash
53
- chmod +x trim-convert.sh
54
- ```
55
-
56
- **Note:** The `chmod +x` command grants execute permission to the script file. This is required for shell scripts to run.
57
-
58
- **Step 3:** Test the installation
59
- ```bash
60
- ./trim-convert.sh --help
61
- ```
62
-
63
- ### System-wide Installation (Optional)
64
-
65
- **Step 1:** Copy script to system directory
66
- ```bash
67
- sudo cp trim-convert.sh /usr/local/bin/trim-convert
68
- ```
69
-
70
- **Note:** The `sudo` command requires administrator privileges to copy files to system directories like `/usr/local/bin/`.
71
-
72
- **Step 2:** Test system-wide access
73
- ```bash
74
- trim-convert --help
75
- ```
76
-
77
- Now you can use `trim-convert` from any directory.
78
-
79
- ## Gradio Web Interface
80
-
81
- For an interactive video trimming experience, use the web interface:
82
-
83
- ![Video Trimmer Interface](demo/screenshot-ui.jpg)
84
-
85
- ### Quick Start
86
- ```bash
87
- # Install dependencies
88
  pip install -r requirements.txt
89
-
90
- # Launch the web interface
91
- ./run_demo.sh
92
  ```
93
 
94
- ### Features
95
- - **Video Upload**: Drag & drop MP4/MOV/AVI files or load from Google Drive
96
- - **Google Drive Integration**: Browse your entire Google Drive to pick videos
97
- - **Visual Trimming**: Scrub sliders to find exact start/end points
98
- - **Live Preview**: Video seeks to slider position for precise editing
99
- - **Audio Playback**: Built-in player for extracted audio
100
- - **Download**: Get both trimmed video and AAC audio files
101
- - **Google Drive Upload**: Upload trimmed files back to any folder in your Google Drive
102
-
103
- The web interface automatically converts times and calls the command-line script for processing.
104
-
105
- ### Google Drive Setup (Optional)
106
-
107
- To enable Google Drive integration:
108
-
109
- 1. **Create OAuth credentials**: Follow instructions in `SIMPLE_GOOGLE_SETUP.md`
110
- 2. **Download `oauth_credentials.json`** and place in this directory
111
- 3. **Run the app** - it will open your browser for one-time authentication
112
- 4. **Done!** Browse your entire Google Drive and upload results back
113
-
114
- **Note**: Google Drive integration is completely optional - the app works perfectly without it.
115
-
116
- ## File Permissions
117
-
118
- ### Understanding Script Permissions
119
-
120
- Shell scripts require execute permission to run. When you download a script, it typically doesn't have execute permission by default for security reasons.
121
-
122
- ### Setting Execute Permission
123
-
124
- ```bash
125
- chmod +x trim-convert.sh
126
- ```
127
-
128
- ### Verifying Permissions
129
-
130
- Check if the script has execute permission:
131
- ```bash
132
- ls -l trim-convert.sh
133
- ```
134
-
135
- Look for `x` in the permissions (e.g., `-rwxr-xr-x`). The `x` indicates execute permission.
136
-
137
- ### Common Permission Issues
138
-
139
- - **"Permission denied"**: Script lacks execute permission - run `chmod +x`
140
- - **"Operation not permitted"**: Need `sudo` for system directories
141
- - **"Command not found"**: Script not in current directory or PATH
142
-
143
- ## Command Line Usage
144
-
145
- ```bash
146
- ./trim-convert.sh [options] input.mp4
147
- ```
148
-
149
- ### Options
150
-
151
- | Option | Description |
152
- |--------|-------------|
153
- | `-s, --start TIME` | Start time (format: HH:MM:SS or seconds) |
154
- | `-e, --end TIME` | End time (format: HH:MM:SS or seconds) |
155
- | `-o, --output PREFIX` | Output file prefix (default: "trimmed") |
156
- | `-h, --help` | Show help message |
157
-
158
- ### Examples
159
-
160
- #### Basic Trimming
161
- ```bash
162
- # Trim from 1m30s to 5m45s
163
- ./trim-convert.sh -s 00:01:30 -e 00:05:45 video.mp4
164
-
165
- # Trim from start to 10 minutes
166
- ./trim-convert.sh -e 00:10:00 video.mp4
167
-
168
- # Trim from 2 minutes to the end
169
- ./trim-convert.sh -s 00:02:00 video.mp4
170
- ```
171
-
172
- #### Advanced Usage
173
- ```bash
174
- # Custom output name
175
- ./trim-convert.sh -o my_clip -s 00:01:30 -e 00:05:45 video.mp4
176
-
177
- # Process entire file (extract audio only)
178
- ./trim-convert.sh video.mp4
179
-
180
- # Using seconds instead of HH:MM:SS format
181
- ./trim-convert.sh -s 90 -e 345 video.mp4
182
-
183
- # Batch processing multiple files
184
- for file in *.mp4; do
185
- ./trim-convert.sh -s 00:00:10 -e 00:01:00 "$file"
186
- done
187
- ```
188
-
189
- #### Time Format Options
190
- - **HH:MM:SS**: `00:01:30` (1 minute 30 seconds)
191
- - **MM:SS**: `01:30` (1 minute 30 seconds)
192
- - **Seconds**: `90` (90 seconds)
193
- - **Decimal seconds**: `90.5` (90.5 seconds)
194
-
195
- ## How It Works
196
 
197
- 1. The script analyzes the input video to determine if it can use stream copying (no re-encoding) based on keyframe positioning
198
- 2. It attempts to use the fastest method possible while maintaining quality
199
- 3. If stream copying would result in imprecise cuts, it falls back to re-encoding
200
- 4. The audio track is extracted as a separate AAC file
 
 
 
201
 
202
- ## Output
203
 
204
- - `PREFIX.mp4`: The trimmed video file
205
- - `PREFIX.aac`: The extracted audio file
 
 
206
 
207
  ## Technical Details
208
 
209
- - Uses ffmpeg's stream copying (`-c copy`) when possible to avoid quality loss
210
- - Falls back to high-quality, fast encoding when necessary (`-c:v libx264 -preset ultrafast -crf 17`)
211
- - Handles keyframe detection for optimal cutting points
212
- - Works with both relative (seconds) and absolute (HH:MM:SS) time formats
213
-
214
- ## Demo Videos
215
-
216
- This repository includes sample copyright-free videos for testing:
217
-
218
- | File | Description | Source | License |
219
- |------|-------------|---------|---------|
220
- | `demo/sample-10s.mp4` | 10-second test pattern | Generated with ffmpeg | Public Domain |
221
- | `demo/sample-30s.mp4` | 30-second test pattern | Generated with ffmpeg | Public Domain |
222
-
223
- All demo videos are verified to be free to use, modify, and distribute.
224
-
225
- ## Performance Notes
226
-
227
- - **Stream copying**: Fastest method, preserves original quality
228
- - **Re-encoding**: Used when precision is required, high-quality preset
229
- - **Memory usage**: Minimal - processes videos without loading entire file into memory
230
- - **Supported formats**: MP4, AVI, MOV, MKV (output always MP4)
231
-
232
- ## Troubleshooting
233
 
234
- ### Common Issues
235
 
236
- | Problem | Solution |
237
- |---------|----------|
238
- | "ffmpeg not found" | Install ffmpeg using your package manager |
239
- | "Permission denied" | Run `chmod +x trim-convert.sh` to make script executable |
240
- | "Invalid time format" | Use HH:MM:SS or seconds format |
241
- | "No keyframes found" | Video will be re-encoded (slower but precise) |
242
- | "Command not found" | Check if script is in current directory or PATH |
243
- | "Operation not permitted" | Use `sudo` for system-wide installation |
244
 
245
- ### Performance Tips
246
 
247
- - Use stream copying when possible by aligning cuts with keyframes
248
- - For bulk processing, consider processing multiple files in parallel
249
- - Use SSD storage for better I/O performance with large files
250
 
251
- ## Requirements
252
-
253
- - **ffmpeg** (version 4.0 or higher)
254
- - **Bash shell** (version 4.0 or higher recommended)
255
- - **Available disk space** equal to at least 2x the size of your largest video file
256
 
257
  ## License
258
 
259
- MIT License - See [LICENSE](https://github.com/nipunbatra/video-toolkit/blob/main/LICENSE) file for details
260
-
261
- ## Contributing
262
-
263
- Contributions are welcome! Please feel free to submit a Pull Request.
264
-
265
- ---
266
-
267
- ## 📖 Documentation Website
268
-
269
- This repository has an auto-generated documentation website at: **https://nipunbatra.github.io/video-toolkit**
270
-
271
- ### How Documentation Works
272
-
273
- - **Single source**: Edit only this `README.md` file
274
- - **Auto-build**: GitHub Actions automatically updates the website
275
- - **No manual work**: Never touch the `docs/` folder
276
- - **Live updates**: Changes appear on the website when you push to main
277
-
278
- ### Making Documentation Changes
279
-
280
- 1. **Edit this file**: `README.md` (you're reading it now!)
281
- 2. **Commit and push**: `git add . && git commit -m "Update docs" && git push`
282
- 3. **Done**: Website updates automatically in ~2 minutes
283
-
284
- **⚠️ Important**: Never edit files in the `docs/` folder - they're auto-generated!
 
1
+ ---
2
+ title: Video Trimmer Tool
3
+ emoji: ✂️
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: gradio
7
+ sdk_version: 4.0.0
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ ---
 
 
 
 
12
 
13
+ # Video Trimmer Tool
 
14
 
15
+ A fast and efficient video trimming toolkit with an interactive web interface for MP4 video processing. Features visual trimming with drag-to-scrub sliders and automatic audio extraction.
16
 
17
+ 🚀 **Try it live on Hugging Face Spaces!**
 
 
 
 
 
18
 
19
+ 📖 **Full Documentation**: https://nipunbatra.github.io/video-toolkit/
20
 
21
+ ## Quick Start
22
 
23
+ ### Hugging Face Spaces (Recommended)
24
+ The easiest way to use this tool is through our Hugging Face Space - no installation required!
 
 
25
 
26
+ ### Local Installation
 
 
 
 
 
 
 
27
  ```bash
28
  git clone https://github.com/nipunbatra/video-toolkit.git
29
  cd video-toolkit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  pip install -r requirements.txt
31
+ python app.py
 
 
32
  ```
33
 
34
+ ## Features
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
+ - **Interactive Web Interface**: Visual video scrubbing with drag-to-trim sliders
37
+ - **Smart Trimming**: Find exact cut points with real-time video seeking
38
+ - **Audio Extraction**: Automatic AAC extraction with built-in player
39
+ - **Google Drive Integration**: Load videos from and upload results to Google Drive (local only)
40
+ - **Multiple Formats**: Support for MP4, MOV, AVI, and MKV files
41
+ - **Fast Processing**: Stream copying when possible for speed
42
+ - **Command Line Tool**: Fast bash script for automated processing
43
 
44
+ ## How to Use
45
 
46
+ 1. **Upload Video**: Drag & drop your video file or click to upload
47
+ 2. **Set Trim Points**: Use the sliders to scrub through the video and find your desired start/end points
48
+ 3. **Trim**: Click "Trim Video" to process
49
+ 4. **Download**: Get your trimmed video and extracted audio files
50
 
51
  ## Technical Details
52
 
53
+ - Uses ffmpeg for high-quality video processing
54
+ - Preserves original quality when possible through stream copying
55
+ - Falls back to fast, high-quality encoding when precision is required
56
+ - Outputs web-optimized MP4 video and AAC audio files
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
+ ## Supported Formats
59
 
60
+ - **Input**: MP4, MOV, AVI, MKV
61
+ - **Output**: MP4 video + AAC audio
 
 
 
 
 
 
62
 
63
+ ## Full Documentation
64
 
65
+ For complete installation instructions, command-line usage, Google Drive setup, and advanced features, visit our documentation website:
 
 
66
 
67
+ **📖 https://nipunbatra.github.io/video-toolkit/**
 
 
 
 
68
 
69
  ## License
70
 
71
+ MIT License - See [LICENSE](LICENSE) file for details.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README_DOCS.md ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ A fast and efficient video trimming toolkit with both a **web interface** and **command-line tool** for MP4 video processing. Features visual trimming with drag-to-scrub sliders and automatic audio extraction.
2
+
3
+ **Documentation**: https://nipunbatra.github.io/video-toolkit/
4
+
5
+ ## Features
6
+
7
+ - **Web Interface**: Interactive Gradio demo with drag-to-trim sliders
8
+ - **Google Drive Integration**: Load videos from and upload results to Google Drive
9
+ - **Command Line**: Fast bash script for automated processing
10
+ - **Smart Trimming**: Visual video scrubbing to find exact cut points
11
+ - **Audio Extraction**: Automatic AAC extraction with built-in player
12
+ - **Minimal Processing**: Stream copying when possible for speed
13
+ - **Cross-Platform**: Works on macOS, Linux, and Windows WSL
14
+
15
+ ## Prerequisites
16
+
17
+ - **ffmpeg** (version 4.0 or higher) - The core dependency for video processing
18
+ - **Bash shell** - Available on macOS, Linux, and Windows WSL
19
+
20
+ ### Installing ffmpeg
21
+
22
+ | Platform | Command |
23
+ |----------|---------|
24
+ | macOS | `brew install ffmpeg` |
25
+ | Ubuntu/Debian | `sudo apt update && sudo apt install ffmpeg` |
26
+ | Fedora/CentOS | `sudo dnf install ffmpeg` |
27
+ | Windows | Download from [ffmpeg.org](https://ffmpeg.org/download.html) or use `winget install FFmpeg` |
28
+
29
+ ## Installation
30
+
31
+ ### Quick Install (Recommended)
32
+
33
+ **Step 1:** Download and make executable in one step
34
+ ```bash
35
+ curl -O https://raw.githubusercontent.com/nipunbatra/video-toolkit/main/trim-convert.sh && chmod +x trim-convert.sh
36
+ ```
37
+
38
+ **Step 2:** Verify the script works
39
+ ```bash
40
+ ./trim-convert.sh --help
41
+ ```
42
+
43
+ ### Manual Install
44
+
45
+ **Step 1:** Clone this repository
46
+ ```bash
47
+ git clone https://github.com/nipunbatra/video-toolkit.git
48
+ cd video-toolkit
49
+ ```
50
+
51
+ **Step 2:** Make the script executable
52
+ ```bash
53
+ chmod +x trim-convert.sh
54
+ ```
55
+
56
+ **Note:** The `chmod +x` command grants execute permission to the script file. This is required for shell scripts to run.
57
+
58
+ **Step 3:** Test the installation
59
+ ```bash
60
+ ./trim-convert.sh --help
61
+ ```
62
+
63
+ ### System-wide Installation (Optional)
64
+
65
+ **Step 1:** Copy script to system directory
66
+ ```bash
67
+ sudo cp trim-convert.sh /usr/local/bin/trim-convert
68
+ ```
69
+
70
+ **Note:** The `sudo` command requires administrator privileges to copy files to system directories like `/usr/local/bin/`.
71
+
72
+ **Step 2:** Test system-wide access
73
+ ```bash
74
+ trim-convert --help
75
+ ```
76
+
77
+ Now you can use `trim-convert` from any directory.
78
+
79
+ ## Gradio Web Interface
80
+
81
+ For an interactive video trimming experience, use the web interface:
82
+
83
+ ![Video Trimmer Interface](demo/screenshot-ui.jpg)
84
+
85
+ ### Quick Start
86
+ ```bash
87
+ # Install dependencies
88
+ pip install -r requirements.txt
89
+
90
+ # Launch the web interface
91
+ ./run_demo.sh
92
+ ```
93
+
94
+ ### Features
95
+ - **Video Upload**: Drag & drop MP4/MOV/AVI files or load from Google Drive
96
+ - **Google Drive Integration**: Browse your entire Google Drive to pick videos
97
+ - **Visual Trimming**: Scrub sliders to find exact start/end points
98
+ - **Live Preview**: Video seeks to slider position for precise editing
99
+ - **Audio Playback**: Built-in player for extracted audio
100
+ - **Download**: Get both trimmed video and AAC audio files
101
+ - **Google Drive Upload**: Upload trimmed files back to any folder in your Google Drive
102
+
103
+ The web interface automatically converts times and calls the command-line script for processing.
104
+
105
+ ### Google Drive Setup (Optional)
106
+
107
+ To enable Google Drive integration:
108
+
109
+ 1. **Create OAuth credentials**: Follow instructions in `SIMPLE_GOOGLE_SETUP.md`
110
+ 2. **Download `oauth_credentials.json`** and place in this directory
111
+ 3. **Run the app** - it will open your browser for one-time authentication
112
+ 4. **Done!** Browse your entire Google Drive and upload results back
113
+
114
+ **Note**: Google Drive integration is completely optional - the app works perfectly without it.
115
+
116
+ ## File Permissions
117
+
118
+ ### Understanding Script Permissions
119
+
120
+ Shell scripts require execute permission to run. When you download a script, it typically doesn't have execute permission by default for security reasons.
121
+
122
+ ### Setting Execute Permission
123
+
124
+ ```bash
125
+ chmod +x trim-convert.sh
126
+ ```
127
+
128
+ ### Verifying Permissions
129
+
130
+ Check if the script has execute permission:
131
+ ```bash
132
+ ls -l trim-convert.sh
133
+ ```
134
+
135
+ Look for `x` in the permissions (e.g., `-rwxr-xr-x`). The `x` indicates execute permission.
136
+
137
+ ### Common Permission Issues
138
+
139
+ - **"Permission denied"**: Script lacks execute permission - run `chmod +x`
140
+ - **"Operation not permitted"**: Need `sudo` for system directories
141
+ - **"Command not found"**: Script not in current directory or PATH
142
+
143
+ ## Command Line Usage
144
+
145
+ ```bash
146
+ ./trim-convert.sh [options] input.mp4
147
+ ```
148
+
149
+ ### Options
150
+
151
+ | Option | Description |
152
+ |--------|-------------|
153
+ | `-s, --start TIME` | Start time (format: HH:MM:SS or seconds) |
154
+ | `-e, --end TIME` | End time (format: HH:MM:SS or seconds) |
155
+ | `-o, --output PREFIX` | Output file prefix (default: "trimmed") |
156
+ | `-h, --help` | Show help message |
157
+
158
+ ### Examples
159
+
160
+ #### Basic Trimming
161
+ ```bash
162
+ # Trim from 1m30s to 5m45s
163
+ ./trim-convert.sh -s 00:01:30 -e 00:05:45 video.mp4
164
+
165
+ # Trim from start to 10 minutes
166
+ ./trim-convert.sh -e 00:10:00 video.mp4
167
+
168
+ # Trim from 2 minutes to the end
169
+ ./trim-convert.sh -s 00:02:00 video.mp4
170
+ ```
171
+
172
+ #### Advanced Usage
173
+ ```bash
174
+ # Custom output name
175
+ ./trim-convert.sh -o my_clip -s 00:01:30 -e 00:05:45 video.mp4
176
+
177
+ # Process entire file (extract audio only)
178
+ ./trim-convert.sh video.mp4
179
+
180
+ # Using seconds instead of HH:MM:SS format
181
+ ./trim-convert.sh -s 90 -e 345 video.mp4
182
+
183
+ # Batch processing multiple files
184
+ for file in *.mp4; do
185
+ ./trim-convert.sh -s 00:00:10 -e 00:01:00 "$file"
186
+ done
187
+ ```
188
+
189
+ #### Time Format Options
190
+ - **HH:MM:SS**: `00:01:30` (1 minute 30 seconds)
191
+ - **MM:SS**: `01:30` (1 minute 30 seconds)
192
+ - **Seconds**: `90` (90 seconds)
193
+ - **Decimal seconds**: `90.5` (90.5 seconds)
194
+
195
+ ## How It Works
196
+
197
+ 1. The script analyzes the input video to determine if it can use stream copying (no re-encoding) based on keyframe positioning
198
+ 2. It attempts to use the fastest method possible while maintaining quality
199
+ 3. If stream copying would result in imprecise cuts, it falls back to re-encoding
200
+ 4. The audio track is extracted as a separate AAC file
201
+
202
+ ## Output
203
+
204
+ - `PREFIX.mp4`: The trimmed video file
205
+ - `PREFIX.aac`: The extracted audio file
206
+
207
+ ## Technical Details
208
+
209
+ - Uses ffmpeg's stream copying (`-c copy`) when possible to avoid quality loss
210
+ - Falls back to high-quality, fast encoding when necessary (`-c:v libx264 -preset ultrafast -crf 17`)
211
+ - Handles keyframe detection for optimal cutting points
212
+ - Works with both relative (seconds) and absolute (HH:MM:SS) time formats
213
+
214
+ ## Demo Videos
215
+
216
+ This repository includes sample copyright-free videos for testing:
217
+
218
+ | File | Description | Source | License |
219
+ |------|-------------|---------|---------|
220
+ | `demo/sample-10s.mp4` | 10-second test pattern | Generated with ffmpeg | Public Domain |
221
+ | `demo/sample-30s.mp4` | 30-second test pattern | Generated with ffmpeg | Public Domain |
222
+
223
+ All demo videos are verified to be free to use, modify, and distribute.
224
+
225
+ ## Performance Notes
226
+
227
+ - **Stream copying**: Fastest method, preserves original quality
228
+ - **Re-encoding**: Used when precision is required, high-quality preset
229
+ - **Memory usage**: Minimal - processes videos without loading entire file into memory
230
+ - **Supported formats**: MP4, AVI, MOV, MKV (output always MP4)
231
+
232
+ ## Troubleshooting
233
+
234
+ ### Common Issues
235
+
236
+ | Problem | Solution |
237
+ |---------|----------|
238
+ | "ffmpeg not found" | Install ffmpeg using your package manager |
239
+ | "Permission denied" | Run `chmod +x trim-convert.sh` to make script executable |
240
+ | "Invalid time format" | Use HH:MM:SS or seconds format |
241
+ | "No keyframes found" | Video will be re-encoded (slower but precise) |
242
+ | "Command not found" | Check if script is in current directory or PATH |
243
+ | "Operation not permitted" | Use `sudo` for system-wide installation |
244
+
245
+ ### Performance Tips
246
+
247
+ - Use stream copying when possible by aligning cuts with keyframes
248
+ - For bulk processing, consider processing multiple files in parallel
249
+ - Use SSD storage for better I/O performance with large files
250
+
251
+ ## Requirements
252
+
253
+ - **ffmpeg** (version 4.0 or higher)
254
+ - **Bash shell** (version 4.0 or higher recommended)
255
+ - **Available disk space** equal to at least 2x the size of your largest video file
256
+
257
+ ## License
258
+
259
+ MIT License - See [LICENSE](https://github.com/nipunbatra/video-toolkit/blob/main/LICENSE) file for details
260
+
261
+ ## Contributing
262
+
263
+ Contributions are welcome! Please feel free to submit a Pull Request.
264
+
265
+ ---
266
+
267
+ ## 📖 Documentation Website
268
+
269
+ This repository has an auto-generated documentation website at: **https://nipunbatra.github.io/video-toolkit**
270
+
271
+ ### How Documentation Works
272
+
273
+ - **Single source**: Edit only this `README.md` file
274
+ - **Auto-build**: GitHub Actions automatically updates the website
275
+ - **No manual work**: Never touch the `docs/` folder
276
+ - **Live updates**: Changes appear on the website when you push to main
277
+
278
+ ### Making Documentation Changes
279
+
280
+ 1. **Edit this file**: `README.md` (you're reading it now!)
281
+ 2. **Commit and push**: `git add . && git commit -m "Update docs" && git push`
282
+ 3. **Done**: Website updates automatically in ~2 minutes
283
+
284
+ **⚠️ Important**: Never edit files in the `docs/` folder - they're auto-generated!
README_HF.md ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Video Trimmer Tool
2
+
3
+ A fast and efficient video trimming toolkit with an interactive web interface for MP4 video processing. Features visual trimming with drag-to-scrub sliders and automatic audio extraction.
4
+
5
+ ## Features
6
+
7
+ - **Interactive Web Interface**: Visual video scrubbing with drag-to-trim sliders
8
+ - **Smart Trimming**: Find exact cut points with real-time video seeking
9
+ - **Audio Extraction**: Automatic AAC extraction with built-in player
10
+ - **Multiple Formats**: Support for MP4, MOV, AVI, and MKV files
11
+ - **Fast Processing**: Stream copying when possible for speed
12
+ - **Download**: Get both trimmed video and extracted audio files
13
+
14
+ ## How to Use
15
+
16
+ 1. **Upload Video**: Drag & drop your video file or click to upload
17
+ 2. **Set Trim Points**: Use the sliders to scrub through the video and find your desired start/end points
18
+ 3. **Trim**: Click "Trim Video" to process
19
+ 4. **Download**: Get your trimmed video and extracted audio files
20
+
21
+ ## Technical Details
22
+
23
+ - Uses ffmpeg for high-quality video processing
24
+ - Preserves original quality when possible through stream copying
25
+ - Falls back to fast, high-quality encoding when precision is required
26
+ - Outputs web-optimized MP4 video and AAC audio files
27
+
28
+ ## Supported Formats
29
+
30
+ - **Input**: MP4, MOV, AVI, MKV
31
+ - **Output**: MP4 video + AAC audio
32
+
33
+ The tool automatically handles format conversion and optimization for web playback.
app.py ADDED
@@ -0,0 +1,695 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import subprocess
3
+ import os
4
+ import tempfile
5
+ import shutil
6
+ import logging
7
+ import time
8
+ from pathlib import Path
9
+ from native_drive_picker import GoogleDrivePickerManager, get_native_picker_instructions, GOOGLE_DRIVE_AVAILABLE
10
+
11
+ # Set up logging
12
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
13
+ logger = logging.getLogger(__name__)
14
+
15
+ def process_video_trim(video_file, start_time, end_time):
16
+ """Process video trimming using the trim-convert.sh script"""
17
+ logger.info(f"🎬 Starting trim process: file={video_file}, start={start_time}, end={end_time}")
18
+
19
+ if not video_file or start_time is None or end_time is None:
20
+ error_msg = "Please provide video file and both start/end times"
21
+ logger.error(f"❌ {error_msg}")
22
+ return None, None, None, error_msg
23
+
24
+ try:
25
+ # start_time and end_time are now numbers (seconds) from sliders
26
+ start_seconds = float(start_time)
27
+ end_seconds = float(end_time)
28
+
29
+ logger.info(f"📊 Parsed times: start={start_seconds}s, end={end_seconds}s")
30
+
31
+ if start_seconds >= end_seconds:
32
+ error_msg = "Start time must be less than end time"
33
+ logger.error(f"❌ {error_msg}")
34
+ return None, None, None, error_msg
35
+
36
+ # Check if input file exists
37
+ if not os.path.exists(video_file):
38
+ error_msg = f"Input video file not found: {video_file}"
39
+ logger.error(f"❌ {error_msg}")
40
+ return None, None, None, error_msg
41
+
42
+ # Create temporary directory for output
43
+ temp_dir = tempfile.mkdtemp()
44
+ logger.info(f"📁 Created temp directory: {temp_dir}")
45
+
46
+ input_path = video_file
47
+
48
+ # Get the base filename without extension
49
+ base_name = Path(input_path).stem
50
+ output_prefix = os.path.join(temp_dir, f"{base_name}_trimmed")
51
+
52
+ # The script will create these files based on the prefix
53
+ output_video = f"{output_prefix}.mp4"
54
+ output_audio = f"{output_prefix}.aac"
55
+
56
+ logger.info(f"📤 Output files will be: video={output_video}, audio={output_audio}")
57
+
58
+ # Check if trim-convert.sh script exists
59
+ script_path = "./trim-convert.sh"
60
+ if not os.path.exists(script_path):
61
+ error_msg = f"trim-convert.sh script not found at: {script_path}"
62
+ logger.error(f"❌ {error_msg}")
63
+ return None, None, None, error_msg
64
+
65
+ # Convert seconds to HH:MM:SS format for the script
66
+ def seconds_to_time(seconds):
67
+ hours = int(seconds // 3600)
68
+ minutes = int((seconds % 3600) // 60)
69
+ secs = seconds % 60
70
+ return f"{hours:02d}:{minutes:02d}:{secs:06.3f}"
71
+
72
+ start_time_str = seconds_to_time(start_seconds)
73
+ end_time_str = seconds_to_time(end_seconds)
74
+
75
+ logger.info(f"🕒 Converted times: start={start_time_str}, end={end_time_str}")
76
+
77
+ # Call the trim-convert.sh script with proper format
78
+ cmd = [
79
+ "bash", script_path,
80
+ "-s", start_time_str,
81
+ "-e", end_time_str,
82
+ "-o", output_prefix,
83
+ input_path
84
+ ]
85
+
86
+ logger.info(f"🚀 Running command: {' '.join(cmd)}")
87
+
88
+ result = subprocess.run(cmd, capture_output=True, text=True, cwd='.')
89
+
90
+ logger.info(f"📋 Command finished with return code: {result.returncode}")
91
+ logger.info(f"📤 STDOUT: {result.stdout}")
92
+ if result.stderr:
93
+ logger.warning(f"⚠️ STDERR: {result.stderr}")
94
+
95
+ if result.returncode == 0:
96
+ # Check if files were created
97
+ video_exists = os.path.exists(output_video)
98
+ audio_exists = os.path.exists(output_audio)
99
+
100
+ logger.info(f"📁 File check: video_exists={video_exists}, audio_exists={audio_exists}")
101
+
102
+ if video_exists and audio_exists:
103
+ video_size = os.path.getsize(output_video)
104
+ audio_size = os.path.getsize(output_audio)
105
+ logger.info(f"📊 File sizes: video={video_size} bytes, audio={audio_size} bytes")
106
+
107
+ # Check if video file is valid and convert for better web compatibility
108
+ try:
109
+ test_duration = get_video_duration(output_video)
110
+ logger.info(f"✅ Output video duration: {test_duration} seconds")
111
+ if test_duration == 0:
112
+ logger.warning("⚠️ Output video duration is 0, may have encoding issues")
113
+
114
+ # Check if trimmed video is web-compatible, if not, convert only the headers
115
+ display_video = output_video # Start with original
116
+
117
+ # Quick check if video might have compatibility issues
118
+ try:
119
+ # Test if ffprobe can read the file properly
120
+ probe_cmd = ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", output_video]
121
+ probe_result = subprocess.run(probe_cmd, capture_output=True, text=True)
122
+
123
+ if probe_result.returncode == 0:
124
+ import json
125
+ probe_data = json.loads(probe_result.stdout)
126
+ format_info = probe_data.get('format', {})
127
+
128
+ # Check if it needs web optimization
129
+ needs_conversion = False
130
+
131
+ # If the file has issues or isn't web-optimized, do a quick fix
132
+ if needs_conversion or True: # Always do quick web optimization for now
133
+ web_video_path = os.path.join(temp_dir, f"{base_name}_web.mp4")
134
+
135
+ # Quick web compatibility fix - just fix headers and ensure proper format
136
+ web_convert_cmd = [
137
+ "ffmpeg", "-y", "-i", output_video,
138
+ "-c", "copy", # Copy streams (fast)
139
+ "-movflags", "+faststart", # Optimize for web
140
+ "-f", "mp4", # Ensure MP4 format
141
+ web_video_path
142
+ ]
143
+
144
+ logger.info(f"🌐 Quick web optimization (stream copy)...")
145
+ web_result = subprocess.run(web_convert_cmd, capture_output=True, text=True)
146
+
147
+ if web_result.returncode == 0 and os.path.exists(web_video_path):
148
+ web_size = os.path.getsize(web_video_path)
149
+ logger.info(f"✅ Web-optimized video: {web_video_path} ({web_size} bytes)")
150
+ display_video = web_video_path
151
+
152
+ # Verify the optimized video
153
+ web_duration = get_video_duration(web_video_path)
154
+ logger.info(f"🎬 Optimized video duration: {web_duration} seconds")
155
+ else:
156
+ logger.warning(f"⚠️ Quick optimization failed: {web_result.stderr}")
157
+ logger.info("Using original trimmed video")
158
+ else:
159
+ logger.warning("⚠️ Could not analyze trimmed video, using as-is")
160
+
161
+ except Exception as e:
162
+ logger.warning(f"⚠️ Video analysis failed: {e}, using original")
163
+
164
+ except Exception as e:
165
+ logger.warning(f"⚠️ Could not verify output video: {e}")
166
+ display_video = output_video
167
+
168
+ # Create MP3 version for audio player (better browser compatibility)
169
+ timestamp = str(int(time.time() * 1000))
170
+ temp_audio_dir = os.path.dirname(output_audio)
171
+ audio_player_file = os.path.join(temp_audio_dir, f"player_audio_{timestamp}.mp3")
172
+
173
+ # Convert AAC to MP3 for better browser support
174
+ convert_cmd = [
175
+ "ffmpeg", "-y", "-i", output_audio,
176
+ "-codec:a", "libmp3lame", "-b:a", "128k",
177
+ audio_player_file
178
+ ]
179
+
180
+ logger.info(f"🔄 Converting audio for player: {' '.join(convert_cmd)}")
181
+ convert_result = subprocess.run(convert_cmd, capture_output=True, text=True)
182
+
183
+ if convert_result.returncode == 0 and os.path.exists(audio_player_file):
184
+ logger.info(f"🎵 Created MP3 audio player file: {audio_player_file}")
185
+ logger.info(f"📊 Audio player file size: {os.path.getsize(audio_player_file)} bytes")
186
+ else:
187
+ logger.warning(f"⚠️ MP3 conversion failed, using original AAC file")
188
+ audio_player_file = output_audio
189
+
190
+ success_msg = f"✅ Successfully trimmed video from {start_seconds:.1f}s to {end_seconds:.1f}s"
191
+
192
+ # No automatic upload - will be done manually after trimming
193
+
194
+ logger.info(success_msg)
195
+ return display_video, audio_player_file, output_audio, success_msg, output_video, output_audio
196
+ else:
197
+ error_msg = f"❌ Output files not created.\n\nScript STDOUT:\n{result.stdout}\n\nScript STDERR:\n{result.stderr}\n\nExpected files:\nVideo: {output_video}\nAudio: {output_audio}"
198
+ logger.error(error_msg)
199
+ return None, None, None, error_msg, None, None
200
+ else:
201
+ error_msg = f"❌ trim-convert.sh failed with return code {result.returncode}\n\nCommand run:\n{' '.join(cmd)}\n\nSTDOUT:\n{result.stdout}\n\nSTDERR:\n{result.stderr}"
202
+ logger.error(error_msg)
203
+ return None, None, None, error_msg
204
+
205
+ except Exception as e:
206
+ error_msg = f"❌ Unexpected error: {str(e)}"
207
+ logger.exception(error_msg)
208
+ return None, None, None, error_msg
209
+
210
+ def get_video_duration(video_file):
211
+ """Get video duration in seconds"""
212
+ if not video_file:
213
+ return 0
214
+
215
+ try:
216
+ logger.info(f"📺 Getting duration for: {video_file}")
217
+
218
+ # Use ffprobe to get video duration
219
+ cmd = [
220
+ "ffprobe", "-v", "quiet", "-print_format", "json",
221
+ "-show_format", "-show_streams", video_file
222
+ ]
223
+ result = subprocess.run(cmd, capture_output=True, text=True)
224
+
225
+ if result.returncode == 0:
226
+ import json
227
+ data = json.loads(result.stdout)
228
+ duration = float(data['format']['duration'])
229
+ logger.info(f"⏱️ Video duration: {duration} seconds")
230
+ return duration
231
+ else:
232
+ logger.warning(f"⚠️ Could not get duration: {result.stderr}")
233
+ return 0
234
+ except Exception as e:
235
+ logger.exception(f"❌ Error getting video duration: {e}")
236
+ return 0
237
+
238
+ def format_time(seconds):
239
+ """Format seconds to mm:ss"""
240
+ if seconds is None:
241
+ return "0:00"
242
+ minutes = int(seconds // 60)
243
+ secs = int(seconds % 60)
244
+ return f"{minutes}:{secs:02d}"
245
+
246
+ def get_video_info(video_file):
247
+ """Get video duration and basic info"""
248
+ if not video_file:
249
+ return "No video uploaded", 0, 0, 0
250
+
251
+ logger.info(f"📹 Processing video upload: {video_file}")
252
+
253
+ duration = get_video_duration(video_file)
254
+ if duration > 0:
255
+ minutes = int(duration // 60)
256
+ seconds = int(duration % 60)
257
+ info = f"📹 Video loaded! Duration: {minutes}:{seconds:02d} ({duration:.1f}s)"
258
+ logger.info(f"✅ {info}")
259
+ return info, duration, 0, duration
260
+ else:
261
+ info = "📹 Video loaded! (Could not determine duration)"
262
+ logger.warning(f"⚠️ {info}")
263
+ return info, 100, 0, 100
264
+
265
+ # Native Google Drive picker functions
266
+ def open_file_picker(drive_manager):
267
+ """Open Google Drive for file selection (full access)"""
268
+ if not drive_manager or not drive_manager.is_available():
269
+ return "❌ Google Drive not available"
270
+
271
+ instructions = drive_manager.open_drive_picker("file")
272
+ return instructions
273
+
274
+ def open_folder_picker(drive_manager):
275
+ """Open Google Drive for folder selection"""
276
+ if not drive_manager or not drive_manager.is_available():
277
+ return "❌ Google Drive not available"
278
+
279
+ instructions = drive_manager.open_drive_picker("folder")
280
+ return instructions
281
+
282
+ def download_from_drive_url(drive_manager, drive_url, custom_filename=""):
283
+ """Download video from Google Drive URL"""
284
+ if not drive_manager or not drive_manager.is_available():
285
+ return None, "❌ Google Drive not available"
286
+
287
+ if not drive_url or not drive_url.strip():
288
+ return None, "⚠️ Please paste a Google Drive link"
289
+
290
+ filename = custom_filename.strip() if custom_filename.strip() else None
291
+ return drive_manager.download_file_from_url(drive_url, filename)
292
+
293
+ def download_from_google_drive(file_id, file_display, drive_manager):
294
+ """Download selected file from Google Drive"""
295
+ if not file_id or not drive_manager or not drive_manager.is_available():
296
+ return None, "❌ No file selected or Google Drive unavailable"
297
+
298
+ try:
299
+ # Extract filename from display string
300
+ filename = file_display.split(' (')[0] if file_display else f"video_{file_id}.mp4"
301
+
302
+ logger.info(f"📥 Downloading {filename} from Google Drive...")
303
+ local_path = drive_manager.download_file(file_id, filename)
304
+
305
+ if local_path and os.path.exists(local_path):
306
+ return local_path, f"✅ Downloaded: {filename}"
307
+ else:
308
+ return None, "❌ Download failed"
309
+ except Exception as e:
310
+ logger.error(f"Error downloading from Google Drive: {e}")
311
+ return None, f"❌ Download error: {str(e)}"
312
+
313
+ # Initialize Google Drive manager
314
+ try:
315
+ drive_manager = GoogleDrivePickerManager()
316
+ drive_available = drive_manager.is_available()
317
+ except Exception as e:
318
+ logger.warning(f"Google Drive initialization failed: {e}")
319
+ drive_manager = None
320
+ drive_available = False
321
+
322
+ # Create the Gradio interface with custom CSS and JS
323
+ custom_css = """
324
+ .video-container video {
325
+ width: 100%;
326
+ max-height: 400px;
327
+ }
328
+ .slider-container {
329
+ margin: 10px 0;
330
+ }
331
+ .drive-section {
332
+ border: 1px solid #e0e0e0;
333
+ padding: 15px;
334
+ border-radius: 8px;
335
+ margin: 10px 0;
336
+ }
337
+ """
338
+
339
+ custom_js = """
340
+ function seekVideo(slider_value, video_id) {
341
+ const video = document.querySelector('#' + video_id + ' video');
342
+ if (video && !isNaN(slider_value)) {
343
+ video.currentTime = slider_value;
344
+ }
345
+ return slider_value;
346
+ }
347
+ """
348
+
349
+ with gr.Blocks(title="Video Trimmer Tool", theme=gr.themes.Soft(), css=custom_css, js=custom_js) as demo:
350
+ gr.Markdown("""
351
+ # 🎬 Video Trimmer Demo
352
+ Upload an MP4 video, set trim points, and generate trimmed video + audio files.
353
+ """)
354
+
355
+ # Native Google Drive picker section
356
+ if drive_available:
357
+ user_email = drive_manager.get_user_info() if drive_manager else "Unknown"
358
+ with gr.Group():
359
+ gr.Markdown("### 🔗 Google Drive Integration (Native Picker)")
360
+ gr.Markdown(f"**👤 Signed in as:** {user_email}")
361
+
362
+ # Video picker section
363
+ with gr.Row():
364
+ with gr.Column(scale=2):
365
+ gr.Markdown("#### 📁 Load Any File from Google Drive")
366
+
367
+ open_picker_btn = gr.Button(
368
+ "🌍 Browse Your Entire Google Drive",
369
+ variant="primary",
370
+ size="lg"
371
+ )
372
+
373
+ picker_instructions = gr.Textbox(
374
+ label="📝 Instructions",
375
+ value="Click the button above to open your full Google Drive - browse any folder!",
376
+ interactive=False,
377
+ lines=6
378
+ )
379
+
380
+ drive_url_input = gr.Textbox(
381
+ label="🔗 Paste Any Google Drive File Link",
382
+ placeholder="https://drive.google.com/file/d/FILE_ID/view...",
383
+ info="Works with any file type - videos, docs, etc. from any folder"
384
+ )
385
+
386
+ custom_filename_input = gr.Textbox(
387
+ label="🏷️ Custom Filename (Optional)",
388
+ placeholder="my_video.mp4"
389
+ )
390
+
391
+ download_from_url_btn = gr.Button(
392
+ "📥 Download Video from Link",
393
+ variant="secondary"
394
+ )
395
+
396
+ with gr.Column(scale=1):
397
+ drive_status = gr.Textbox(
398
+ label="📊 Status",
399
+ value="✅ Ready to pick from Google Drive",
400
+ interactive=False
401
+ )
402
+
403
+ # Simplified note
404
+ gr.Markdown("🚀 **Upload to Google Drive will be available after video trimming.**")
405
+ else:
406
+ with gr.Group():
407
+ gr.Markdown("### 🔗 Google Drive Integration")
408
+ if not GOOGLE_DRIVE_AVAILABLE:
409
+ gr.Markdown("**⚠️ Google Drive libraries not installed.**")
410
+ gr.Markdown("Install with: `pip install google-api-python-client google-auth google-auth-oauthlib`")
411
+ else:
412
+ gr.Markdown("**⚠️ Setup needed:** Create oauth_credentials.json file")
413
+
414
+ with gr.Accordion("📋 Setup Instructions", open=False):
415
+ gr.Markdown(get_native_picker_instructions())
416
+
417
+ with gr.Row():
418
+ with gr.Column(scale=2):
419
+ # Video upload and display
420
+ video_input = gr.File(
421
+ label="📁 Upload MP4 Video",
422
+ file_types=[".mp4", ".mov", ".avi", ".mkv"],
423
+ type="filepath"
424
+ )
425
+
426
+ video_player = gr.Video(
427
+ label="🎥 Video Player",
428
+ show_label=True,
429
+ elem_id="main_video_player",
430
+ elem_classes=["video-container"]
431
+ )
432
+
433
+ video_info = gr.Textbox(
434
+ label="📊 Video Info",
435
+ interactive=False,
436
+ value="Upload a video to see information"
437
+ )
438
+
439
+ with gr.Column(scale=1):
440
+ # Trim controls
441
+ gr.Markdown("### ✂️ Trim Settings")
442
+ gr.Markdown("**🎯 Drag sliders to set trim points:**")
443
+
444
+ with gr.Group():
445
+ gr.Markdown("**🎯 Scrub to find start point:**")
446
+ start_slider = gr.Slider(
447
+ minimum=0,
448
+ maximum=100,
449
+ value=0,
450
+ step=0.1,
451
+ label="⏯️ Start Time (scrub video)",
452
+ info="Drag to seek video and set start position",
453
+ elem_classes=["slider-container"]
454
+ )
455
+
456
+ start_time_display = gr.Textbox(
457
+ label="⏯️ Start Time",
458
+ value="0:00",
459
+ interactive=False,
460
+ info="Current start time"
461
+ )
462
+
463
+ with gr.Group():
464
+ gr.Markdown("**🎯 Scrub to find end point:**")
465
+ end_slider = gr.Slider(
466
+ minimum=0,
467
+ maximum=100,
468
+ value=100,
469
+ step=0.1,
470
+ label="⏹️ End Time (scrub video)",
471
+ info="Drag to seek video and set end position",
472
+ elem_classes=["slider-container"]
473
+ )
474
+
475
+ end_time_display = gr.Textbox(
476
+ label="⏹️ End Time",
477
+ value="1:40",
478
+ interactive=False,
479
+ info="Current end time"
480
+ )
481
+
482
+ trim_btn = gr.Button(
483
+ "✂️ Trim Video",
484
+ variant="primary",
485
+ size="lg"
486
+ )
487
+
488
+ # Note about manual upload
489
+ gr.Markdown("📝 **Note:** Upload options will appear after trimming is complete.")
490
+
491
+ status_msg = gr.Textbox(
492
+ label="📝 Status",
493
+ interactive=False,
494
+ value="Ready to trim..."
495
+ )
496
+
497
+ # Output section
498
+ gr.Markdown("### 📤 Output Files")
499
+
500
+ with gr.Row():
501
+ with gr.Column():
502
+ output_video = gr.Video(
503
+ label="🎬 Trimmed Video",
504
+ show_label=True
505
+ )
506
+
507
+ with gr.Column():
508
+ output_audio_player = gr.Audio(
509
+ label="🎵 Play Extracted Audio",
510
+ show_label=True,
511
+ type="filepath"
512
+ )
513
+
514
+ output_audio_download = gr.File(
515
+ label="💾 Download Audio (AAC)",
516
+ show_label=True
517
+ )
518
+
519
+ # Post-processing upload section (appears after trimming)
520
+ if drive_available:
521
+ with gr.Group(visible=False) as post_upload_section:
522
+ gr.Markdown("### 🚀 Upload Trimmed Files to Google Drive")
523
+
524
+ with gr.Row():
525
+ with gr.Column(scale=2):
526
+ post_open_folder_btn = gr.Button(
527
+ "🌍 Choose Google Drive Upload Folder",
528
+ variant="primary"
529
+ )
530
+
531
+ post_folder_instructions = gr.Textbox(
532
+ label="📝 Folder Instructions",
533
+ value="Click button above to choose where to upload your trimmed files",
534
+ interactive=False,
535
+ lines=4
536
+ )
537
+
538
+ post_upload_folder_url = gr.Textbox(
539
+ label="📁 Upload Folder Link",
540
+ placeholder="https://drive.google.com/drive/folders/FOLDER_ID...",
541
+ info="Leave empty to upload to My Drive root"
542
+ )
543
+
544
+ post_upload_btn = gr.Button(
545
+ "📤 Upload Files to Google Drive",
546
+ variant="secondary",
547
+ size="lg"
548
+ )
549
+
550
+ with gr.Column(scale=1):
551
+ post_upload_status = gr.Textbox(
552
+ label="📊 Upload Status",
553
+ value="Ready to upload",
554
+ interactive=False
555
+ )
556
+
557
+ # Hidden state to store file paths for post-upload
558
+ trimmed_video_path = gr.State(None)
559
+ trimmed_audio_path = gr.State(None)
560
+
561
+ # Event handlers
562
+ def update_video_and_sliders(video_file):
563
+ info, duration, start_val, end_val = get_video_info(video_file)
564
+ return (
565
+ video_file, # video_player
566
+ info, # video_info
567
+ gr.Slider(minimum=0, maximum=duration, value=0, step=0.1), # start_slider
568
+ gr.Slider(minimum=0, maximum=duration, value=duration, step=0.1), # end_slider
569
+ "0:00", # start_time_display
570
+ format_time(duration) # end_time_display
571
+ )
572
+
573
+ def update_start_display(start_val):
574
+ return format_time(start_val)
575
+
576
+ def update_end_display(end_val):
577
+ return format_time(end_val)
578
+
579
+ video_input.change(
580
+ fn=update_video_and_sliders,
581
+ inputs=[video_input],
582
+ outputs=[video_player, video_info, start_slider, end_slider, start_time_display, end_time_display]
583
+ )
584
+
585
+ start_slider.change(
586
+ fn=update_start_display,
587
+ inputs=[start_slider],
588
+ outputs=[start_time_display],
589
+ js="(value) => { const video = document.querySelector('#main_video_player video'); if (video && !isNaN(value)) { video.currentTime = value; } return value; }"
590
+ )
591
+
592
+ end_slider.change(
593
+ fn=update_end_display,
594
+ inputs=[end_slider],
595
+ outputs=[end_time_display],
596
+ js="(value) => { const video = document.querySelector('#main_video_player video'); if (video && !isNaN(value)) { video.currentTime = value; } return value; }"
597
+ )
598
+
599
+ # Google Drive native picker event handlers
600
+ if drive_available:
601
+ # Open file picker (full Google Drive access)
602
+ open_picker_btn.click(
603
+ fn=lambda: open_file_picker(drive_manager),
604
+ outputs=[picker_instructions]
605
+ )
606
+
607
+ # Download from URL
608
+ download_from_url_btn.click(
609
+ fn=lambda url, filename: download_from_drive_url(drive_manager, url, filename),
610
+ inputs=[drive_url_input, custom_filename_input],
611
+ outputs=[video_input, drive_status]
612
+ ).then(
613
+ fn=update_video_and_sliders,
614
+ inputs=[video_input],
615
+ outputs=[video_player, video_info, start_slider, end_slider, start_time_display, end_time_display]
616
+ )
617
+
618
+ # No pre-upload handlers needed
619
+
620
+ # Post-upload event handlers
621
+ post_open_folder_btn.click(
622
+ fn=lambda: open_folder_picker(drive_manager),
623
+ outputs=[post_folder_instructions]
624
+ )
625
+
626
+ def post_upload_files(video_path, audio_path, folder_url):
627
+ if not video_path or not audio_path:
628
+ return "❌ No files to upload"
629
+
630
+ try:
631
+ folder_url_clean = folder_url.strip() if folder_url and folder_url.strip() else None
632
+
633
+ video_success, video_result = drive_manager.upload_file_to_folder(video_path, folder_url_clean)
634
+ audio_success, audio_result = drive_manager.upload_file_to_folder(audio_path, folder_url_clean)
635
+
636
+ if video_success and audio_success:
637
+ return f"✅ Files uploaded successfully:\n• {video_result}\n• {audio_result}"
638
+ elif video_success:
639
+ return f"✅ {video_result}\n❌ Audio upload failed: {audio_result}"
640
+ elif audio_success:
641
+ return f"✅ {audio_result}\n❌ Video upload failed: {video_result}"
642
+ else:
643
+ return f"❌ Upload failed:\n• Video: {video_result}\n• Audio: {audio_result}"
644
+
645
+ except Exception as e:
646
+ return f"❌ Upload error: {str(e)}"
647
+
648
+ post_upload_btn.click(
649
+ fn=post_upload_files,
650
+ inputs=[trimmed_video_path, trimmed_audio_path, post_upload_folder_url],
651
+ outputs=[post_upload_status]
652
+ )
653
+
654
+ # Trim button handler with Google Drive upload support
655
+ if drive_available:
656
+ # Simplified trim function that shows upload section after completion
657
+ def trim_and_show_upload(video_file, start_time, end_time):
658
+ result = process_video_trim(video_file, start_time, end_time)
659
+ display_video, audio_player, audio_download, status, orig_video, orig_audio = result
660
+
661
+ # Show post-upload section if trimming was successful
662
+ show_upload = orig_video is not None and orig_audio is not None
663
+
664
+ return (
665
+ display_video, audio_player, audio_download, status, # Original outputs
666
+ orig_video, orig_audio, # Store paths for post-upload
667
+ gr.Group(visible=show_upload) # Show/hide upload section
668
+ )
669
+
670
+ trim_btn.click(
671
+ fn=trim_and_show_upload,
672
+ inputs=[video_input, start_slider, end_slider],
673
+ outputs=[output_video, output_audio_player, output_audio_download, status_msg,
674
+ trimmed_video_path, trimmed_audio_path, post_upload_section]
675
+ )
676
+ else:
677
+ # No Google Drive available - simple trim only
678
+ def simple_trim(video_file, start_time, end_time):
679
+ result = process_video_trim(video_file, start_time, end_time)
680
+ return result[:4] # Return only the first 4 outputs
681
+
682
+ trim_btn.click(
683
+ fn=simple_trim,
684
+ inputs=[video_input, start_slider, end_slider],
685
+ outputs=[output_video, output_audio_player, output_audio_download, status_msg]
686
+ )
687
+
688
+ if __name__ == "__main__":
689
+ demo.launch(
690
+ server_name="0.0.0.0",
691
+ server_port=None, # Auto-find available port
692
+ share=False,
693
+ show_error=True,
694
+ debug=True
695
+ )