RO-Rtechs commited on
Commit
bf432bc
·
verified ·
1 Parent(s): 6586801

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +239 -238
app.py CHANGED
@@ -1,239 +1,240 @@
1
- import gradio as gr
2
- from gradio import utils
3
- import os
4
- import re
5
- import requests
6
- from concurrent.futures import ThreadPoolExecutor
7
- import time
8
- from yt_dlp import YoutubeDL
9
- import subprocess
10
- import shutil
11
- from typing import List, Tuple
12
-
13
- def sanitize_title(title):
14
- return re.sub(r'[\\/*?:"<>|]', "", title)
15
-
16
- def format_time(seconds):
17
- return time.strftime('%H:%M:%S', time.gmtime(seconds))
18
-
19
- def get_video_info(video_url):
20
- with YoutubeDL({'quiet': True, 'no_warnings': True}) as ydl:
21
- try:
22
- info = ydl.extract_info(video_url, download=False)
23
- formats = info.get('formats', [])
24
-
25
- # Function to safely get bitrate
26
- def get_bitrate(format_dict, key):
27
- return format_dict.get(key, 0) or 0
28
-
29
- # Prefer adaptive formats (separate video and audio)
30
- video_formats = [f for f in formats if f.get('vcodec') != 'none' and f.get('acodec') == 'none']
31
- audio_formats = [f for f in formats if f.get('acodec') != 'none' and f.get('vcodec') == 'none']
32
-
33
- if video_formats and audio_formats:
34
- video_format = max(video_formats, key=lambda f: get_bitrate(f, 'vbr'))
35
- audio_format = max(audio_formats, key=lambda f: get_bitrate(f, 'abr'))
36
- return info['title'], video_format['url'], audio_format['url']
37
- else:
38
- # Fallback to best combined format
39
- combined_formats = [f for f in formats if f.get('vcodec') != 'none' and f.get('acodec') != 'none']
40
- if combined_formats:
41
- best_format = max(combined_formats, key=lambda f: get_bitrate(f, 'tbr'))
42
- return info['title'], best_format['url'], None
43
- else:
44
- raise Exception("No suitable video formats found")
45
- except Exception as e:
46
- raise Exception(f"Error extracting video info: {str(e)}")
47
-
48
- def download_segment(url, start_time, end_time, output_path):
49
- command = [
50
- 'ffmpeg',
51
- '-ss', format_time(start_time),
52
- '-i', url,
53
- '-t', format_time(end_time - start_time),
54
- '-c', 'copy',
55
- '-y',
56
- output_path
57
- ]
58
- process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
59
-
60
- while True:
61
- output = process.stderr.readline()
62
- if output == '' and process.poll() is not None:
63
- break
64
- if output:
65
- yield output.strip()
66
-
67
- rc = process.poll()
68
- return rc == 0
69
-
70
- def combine_segments(video_segments, audio_segments, output_path):
71
- temp_video = 'temp_video.mp4'
72
- temp_audio = 'temp_audio.m4a'
73
-
74
- # Concatenate video segments
75
- with open('video_list.txt', 'w') as f:
76
- for segment in video_segments:
77
- f.write(f"file '{segment}'\n")
78
-
79
- subprocess.run(['ffmpeg', '-f', 'concat', '-safe', '0', '-i', 'video_list.txt', '-c', 'copy', temp_video])
80
-
81
- # Concatenate audio segments if they exist
82
- if audio_segments:
83
- with open('audio_list.txt', 'w') as f:
84
- for segment in audio_segments:
85
- f.write(f"file '{segment}'\n")
86
- subprocess.run(['ffmpeg', '-f', 'concat', '-safe', '0', '-i', 'audio_list.txt', '-c', 'copy', temp_audio])
87
-
88
- # Combine video and audio
89
- subprocess.run(['ffmpeg', '-i', temp_video, '-i', temp_audio, '-c', 'copy', output_path])
90
- else:
91
- shutil.move(temp_video, output_path)
92
-
93
- # Clean up temporary files
94
- os.remove('video_list.txt')
95
- if os.path.exists('audio_list.txt'):
96
- os.remove('audio_list.txt')
97
- if os.path.exists(temp_video):
98
- os.remove(temp_video)
99
- if os.path.exists(temp_audio):
100
- os.remove(temp_audio)
101
-
102
- def add_segment(start_hours, start_minutes, start_seconds, end_hours, end_minutes, end_seconds, segments):
103
- start_time = f"{start_hours:02d}:{start_minutes:02d}:{start_seconds:02d}"
104
- end_time = f"{end_hours:02d}:{end_minutes:02d}:{end_seconds:02d}"
105
- new_segment = f"{start_time}-{end_time}"
106
- new_row = [new_segment]
107
- return segments + [new_row]
108
-
109
- def remove_segment(segments, index):
110
- return segments[:index] + segments[index+1:]
111
-
112
- def move_segment(segments, old_index, new_index):
113
- segments = segments.tolist() # Convert Dataframe to list
114
- segment = segments.pop(old_index)
115
- segments.insert(new_index, segment)
116
- return segments
117
-
118
- def parse_segments(segments: List[str]) -> List[Tuple[int, int]]:
119
- parsed_segments = []
120
- for segment in segments:
121
- start, end = map(lambda x: sum(int(i) * 60 ** j for j, i in enumerate(reversed(x.split(':')))), segment.split('-'))
122
- if start < end:
123
- parsed_segments.append((start, end))
124
- return parsed_segments
125
-
126
- def process_video(video_url, segments, combine, progress=gr.Progress()):
127
- if not video_url.strip():
128
- return 0, "Error: Please provide a valid YouTube URL", None
129
-
130
- # Extract segments from the Dataframe
131
- segment_list = [segment[0] for segment in segments if segment[0].strip()]
132
- parsed_segments = parse_segments(segment_list)
133
- if not parsed_segments:
134
- return 0, "Error: No valid segments provided", None
135
-
136
- output_dir = 'output'
137
- os.makedirs(output_dir, exist_ok=True)
138
-
139
- try:
140
- video_title, video_url, audio_url = get_video_info(video_url)
141
- except Exception as e:
142
- return 0, f"Error: {str(e)}", None
143
-
144
- video_segments = []
145
- audio_segments = []
146
- total_segments = len(parsed_segments)
147
-
148
- for i, (start_time, end_time) in enumerate(parsed_segments):
149
- video_output = os.path.join(output_dir, f"{sanitize_title(video_title)}_video_segment_{i+1}.mp4")
150
- for output in download_segment(video_url, start_time, end_time, video_output):
151
- progress((i / total_segments) + (1 / total_segments) * 0.5)
152
- yield i * 100 // total_segments, f"Downloading video segment {i+1}/{total_segments}: {output}", None
153
- video_segments.append(video_output)
154
-
155
- if audio_url:
156
- audio_output = os.path.join(output_dir, f"{sanitize_title(video_title)}_audio_segment_{i+1}.m4a")
157
- for output in download_segment(audio_url, start_time, end_time, audio_output):
158
- progress((i / total_segments) + (1 / total_segments) * 0.75)
159
- yield i * 100 // total_segments + 50, f"Downloading audio segment {i+1}/{total_segments}: {output}", None
160
- audio_segments.append(audio_output)
161
-
162
- if combine:
163
- output_path = os.path.join(output_dir, f"{sanitize_title(video_title)}_combined.mp4")
164
- combine_segments(video_segments, audio_segments, output_path)
165
- yield 100, f"Segments combined and saved as {output_path}", output_path
166
- else:
167
- # If not combining, return the first video segment (you might want to modify this behavior)
168
- output_path = video_segments[0] if video_segments else None
169
- yield 100, "All segments downloaded successfully", output_path
170
-
171
- # Clean up individual segments if combined
172
- if combine:
173
- for segment in video_segments + audio_segments:
174
- os.remove(segment)
175
-
176
- # Disable Gradio analytics
177
- utils.colab_check = lambda: True
178
-
179
- with gr.Blocks(title="Advanced YouTube Segment Downloader", theme=gr.themes.Soft()) as iface:
180
- gr.Markdown("## Advanced YouTube Segment Downloader")
181
- gr.Markdown("Download segments of YouTube videos using adaptive streaming and ffmpeg, with optional combining.")
182
-
183
- with gr.Row():
184
- video_url = gr.Textbox(label="YouTube URL", placeholder="Enter YouTube URL here")
185
-
186
- with gr.Row():
187
- with gr.Column(scale=1):
188
- with gr.Row():
189
- start_hours = gr.Number(label="Start Hours", minimum=0, maximum=23, step=1, value=0)
190
- start_minutes = gr.Number(label="Start Minutes", minimum=0, maximum=59, step=1, value=0)
191
- start_seconds = gr.Number(label="Start Seconds", minimum=0, maximum=59, step=1, value=0)
192
- with gr.Row():
193
- end_hours = gr.Number(label="End Hours", minimum=0, maximum=23, step=1, value=0)
194
- end_minutes = gr.Number(label="End Minutes", minimum=0, maximum=59, step=1, value=0)
195
- end_seconds = gr.Number(label="End Seconds", minimum=0, maximum=59, step=1, value=0)
196
- add_btn = gr.Button("Add Segment")
197
-
198
- with gr.Column(scale=2):
199
- segments = gr.Dataframe(
200
- headers=["Segment"],
201
- row_count=5,
202
- col_count=1,
203
- interactive=True,
204
- label="Segments"
205
- )
206
-
207
- combine = gr.Checkbox(label="Combine Segments")
208
- submit_btn = gr.Button("Download Segments", variant="primary")
209
-
210
- progress = gr.Slider(label="Progress", minimum=0, maximum=100, step=1)
211
- status = gr.Textbox(label="Status", lines=10)
212
- output_file = gr.File(label="Download Video")
213
-
214
- add_btn.click(
215
- add_segment,
216
- inputs=[start_hours, start_minutes, start_seconds, end_hours, end_minutes, end_seconds, segments],
217
- outputs=[segments]
218
- )
219
-
220
- submit_btn.click(
221
- process_video,
222
- inputs=[video_url, segments, combine],
223
- outputs=[progress, status, output_file]
224
- )
225
-
226
- segments.change(
227
- move_segment,
228
- inputs=[segments, gr.Slider(0, 100, step=1, label="Old Index"), gr.Slider(0, 100, step=1, label="New Index")],
229
- outputs=[segments]
230
- )
231
-
232
- remove_btn = gr.Button("Remove Selected Segment")
233
- remove_btn.click(
234
- remove_segment,
235
- inputs=[segments, gr.Slider(0, 100, step=1, label="Index to Remove")],
236
- outputs=[segments]
237
- )
238
-
 
239
  iface.launch()
 
1
+ import gradio as gr
2
+ from gradio import utils
3
+ import os
4
+ import re
5
+ import requests
6
+ from concurrent.futures import ThreadPoolExecutor
7
+ import time
8
+ from yt_dlp import YoutubeDL
9
+ import subprocess
10
+ import shutil
11
+ from typing import List, Tuple
12
+
13
+ def sanitize_title(title):
14
+ return re.sub(r'[\\/*?:"<>|]', "", title)
15
+
16
+ def format_time(seconds):
17
+ return time.strftime('%H:%M:%S', time.gmtime(seconds))
18
+
19
+ def get_video_info(video_url):
20
+ with YoutubeDL({'quiet': True, 'no_warnings': True}) as ydl:
21
+ try:
22
+ info = ydl.extract_info(video_url, download=False)
23
+ formats = info.get('formats', [])
24
+
25
+ # Function to safely get bitrate
26
+ def get_bitrate(format_dict, key):
27
+ return format_dict.get(key, 0) or 0
28
+
29
+ # Prefer adaptive formats (separate video and audio)
30
+ video_formats = [f for f in formats if f.get('vcodec') != 'none' and f.get('acodec') == 'none']
31
+ audio_formats = [f for f in formats if f.get('acodec') != 'none' and f.get('vcodec') == 'none']
32
+
33
+ if video_formats and audio_formats:
34
+ video_format = max(video_formats, key=lambda f: get_bitrate(f, 'vbr'))
35
+ audio_format = max(audio_formats, key=lambda f: get_bitrate(f, 'abr'))
36
+ return info['title'], video_format['url'], audio_format['url']
37
+ else:
38
+ # Fallback to best combined format
39
+ combined_formats = [f for f in formats if f.get('vcodec') != 'none' and f.get('acodec') != 'none']
40
+ if combined_formats:
41
+ best_format = max(combined_formats, key=lambda f: get_bitrate(f, 'tbr'))
42
+ return info['title'], best_format['url'], None
43
+ else:
44
+ raise Exception("No suitable video formats found")
45
+ except Exception as e:
46
+ raise Exception(f"Error extracting video info: {str(e)}")
47
+
48
+ def download_segment(url, start_time, end_time, output_path):
49
+ command = [
50
+ 'ffmpeg',
51
+ '-ss', format_time(start_time),
52
+ '-i', url,
53
+ '-t', format_time(end_time - start_time),
54
+ '-c', 'copy',
55
+ '-y',
56
+ output_path
57
+ ]
58
+ process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
59
+
60
+ while True:
61
+ output = process.stderr.readline()
62
+ if output == '' and process.poll() is not None:
63
+ break
64
+ if output:
65
+ yield output.strip()
66
+
67
+ rc = process.poll()
68
+ return rc == 0
69
+
70
+ def combine_segments(video_segments, audio_segments, output_path):
71
+ temp_video = 'temp_video.mp4'
72
+ temp_audio = 'temp_audio.m4a'
73
+
74
+ # Concatenate video segments
75
+ with open('video_list.txt', 'w') as f:
76
+ for segment in video_segments:
77
+ f.write(f"file '{segment}'\n")
78
+
79
+ subprocess.run(['ffmpeg', '-f', 'concat', '-safe', '0', '-i', 'video_list.txt', '-c', 'copy', temp_video])
80
+
81
+ # Concatenate audio segments if they exist
82
+ if audio_segments:
83
+ with open('audio_list.txt', 'w') as f:
84
+ for segment in audio_segments:
85
+ f.write(f"file '{segment}'\n")
86
+ subprocess.run(['ffmpeg', '-f', 'concat', '-safe', '0', '-i', 'audio_list.txt', '-c', 'copy', temp_audio])
87
+
88
+ # Combine video and audio
89
+ subprocess.run(['ffmpeg', '-i', temp_video, '-i', temp_audio, '-c', 'copy', output_path])
90
+ else:
91
+ shutil.move(temp_video, output_path)
92
+
93
+ # Clean up temporary files
94
+ os.remove('video_list.txt')
95
+ if os.path.exists('audio_list.txt'):
96
+ os.remove('audio_list.txt')
97
+ if os.path.exists(temp_video):
98
+ os.remove(temp_video)
99
+ if os.path.exists(temp_audio):
100
+ os.remove(temp_audio)
101
+
102
+ def add_segment(start_hours, start_minutes, start_seconds, end_hours, end_minutes, end_seconds, segments):
103
+ start_time = f"{start_hours:02d}:{start_minutes:02d}:{start_seconds:02d}"
104
+ end_time = f"{end_hours:02d}:{end_minutes:02d}:{end_seconds:02d}"
105
+ new_segment = f"{start_time}-{end_time}"
106
+ new_row = [new_segment]
107
+ return segments + [new_row]
108
+
109
+ def remove_segment(segments, index):
110
+ return segments[:index] + segments[index+1:]
111
+
112
+ def move_segment(segments, old_index, new_index):
113
+ segments_list = segments.values.tolist() # Convert Dataframe to list
114
+ if 0 <= old_index < len(segments_list) and 0 <= new_index < len(segments_list):
115
+ segment = segments_list.pop(old_index)
116
+ segments_list.insert(new_index, segment)
117
+ return segments_list
118
+
119
+ def parse_segments(segments: List[str]) -> List[Tuple[int, int]]:
120
+ parsed_segments = []
121
+ for segment in segments:
122
+ start, end = map(lambda x: sum(int(i) * 60 ** j for j, i in enumerate(reversed(x.split(':')))), segment.split('-'))
123
+ if start < end:
124
+ parsed_segments.append((start, end))
125
+ return parsed_segments
126
+
127
+ def process_video(video_url, segments, combine, progress=gr.Progress()):
128
+ if not video_url.strip():
129
+ return 0, "Error: Please provide a valid YouTube URL", None
130
+
131
+ # Extract segments from the Dataframe
132
+ segment_list = [segment[0] for segment in segments if segment[0].strip()]
133
+ parsed_segments = parse_segments(segment_list)
134
+ if not parsed_segments:
135
+ return 0, "Error: No valid segments provided", None
136
+
137
+ output_dir = 'output'
138
+ os.makedirs(output_dir, exist_ok=True)
139
+
140
+ try:
141
+ video_title, video_url, audio_url = get_video_info(video_url)
142
+ except Exception as e:
143
+ return 0, f"Error: {str(e)}", None
144
+
145
+ video_segments = []
146
+ audio_segments = []
147
+ total_segments = len(parsed_segments)
148
+
149
+ for i, (start_time, end_time) in enumerate(parsed_segments):
150
+ video_output = os.path.join(output_dir, f"{sanitize_title(video_title)}_video_segment_{i+1}.mp4")
151
+ for output in download_segment(video_url, start_time, end_time, video_output):
152
+ progress((i / total_segments) + (1 / total_segments) * 0.5)
153
+ yield i * 100 // total_segments, f"Downloading video segment {i+1}/{total_segments}: {output}", None
154
+ video_segments.append(video_output)
155
+
156
+ if audio_url:
157
+ audio_output = os.path.join(output_dir, f"{sanitize_title(video_title)}_audio_segment_{i+1}.m4a")
158
+ for output in download_segment(audio_url, start_time, end_time, audio_output):
159
+ progress((i / total_segments) + (1 / total_segments) * 0.75)
160
+ yield i * 100 // total_segments + 50, f"Downloading audio segment {i+1}/{total_segments}: {output}", None
161
+ audio_segments.append(audio_output)
162
+
163
+ if combine:
164
+ output_path = os.path.join(output_dir, f"{sanitize_title(video_title)}_combined.mp4")
165
+ combine_segments(video_segments, audio_segments, output_path)
166
+ yield 100, f"Segments combined and saved as {output_path}", output_path
167
+ else:
168
+ # If not combining, return the first video segment (you might want to modify this behavior)
169
+ output_path = video_segments[0] if video_segments else None
170
+ yield 100, "All segments downloaded successfully", output_path
171
+
172
+ # Clean up individual segments if combined
173
+ if combine:
174
+ for segment in video_segments + audio_segments:
175
+ os.remove(segment)
176
+
177
+ # Disable Gradio analytics
178
+ utils.colab_check = lambda: True
179
+
180
+ with gr.Blocks(title="Advanced YouTube Segment Downloader", theme=gr.themes.Soft()) as iface:
181
+ gr.Markdown("## Advanced YouTube Segment Downloader")
182
+ gr.Markdown("Download segments of YouTube videos using adaptive streaming and ffmpeg, with optional combining.")
183
+
184
+ with gr.Row():
185
+ video_url = gr.Textbox(label="YouTube URL", placeholder="Enter YouTube URL here")
186
+
187
+ with gr.Row():
188
+ with gr.Column(scale=1):
189
+ with gr.Row():
190
+ start_hours = gr.Number(label="Start Hours", minimum=0, maximum=23, step=1, value=0)
191
+ start_minutes = gr.Number(label="Start Minutes", minimum=0, maximum=59, step=1, value=0)
192
+ start_seconds = gr.Number(label="Start Seconds", minimum=0, maximum=59, step=1, value=0)
193
+ with gr.Row():
194
+ end_hours = gr.Number(label="End Hours", minimum=0, maximum=23, step=1, value=0)
195
+ end_minutes = gr.Number(label="End Minutes", minimum=0, maximum=59, step=1, value=0)
196
+ end_seconds = gr.Number(label="End Seconds", minimum=0, maximum=59, step=1, value=0)
197
+ add_btn = gr.Button("Add Segment")
198
+
199
+ with gr.Column(scale=2):
200
+ segments = gr.Dataframe(
201
+ headers=["Segment"],
202
+ row_count=5,
203
+ col_count=1,
204
+ interactive=True,
205
+ label="Segments"
206
+ )
207
+
208
+ combine = gr.Checkbox(label="Combine Segments")
209
+ submit_btn = gr.Button("Download Segments", variant="primary")
210
+
211
+ progress = gr.Slider(label="Progress", minimum=0, maximum=100, step=1)
212
+ status = gr.Textbox(label="Status", lines=10)
213
+ output_file = gr.File(label="Download Video")
214
+
215
+ add_btn.click(
216
+ add_segment,
217
+ inputs=[start_hours, start_minutes, start_seconds, end_hours, end_minutes, end_seconds, segments],
218
+ outputs=[segments]
219
+ )
220
+
221
+ submit_btn.click(
222
+ process_video,
223
+ inputs=[video_url, segments, combine],
224
+ outputs=[progress, status, output_file]
225
+ )
226
+
227
+ segments.change(
228
+ move_segment,
229
+ inputs=[segments, gr.Slider(0, 100, step=1, label="Old Index"), gr.Slider(0, 100, step=1, label="New Index")],
230
+ outputs=[segments]
231
+ )
232
+
233
+ remove_btn = gr.Button("Remove Selected Segment")
234
+ remove_btn.click(
235
+ remove_segment,
236
+ inputs=[segments, gr.Slider(0, 100, step=1, label="Index to Remove")],
237
+ outputs=[segments]
238
+ )
239
+
240
  iface.launch()