File size: 9,023 Bytes
edc98e3
9312825
 
 
521848e
a3b8611
9312825
 
 
 
edc98e3
 
 
 
 
76cf954
edc98e3
 
 
 
 
 
 
521848e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
901a06f
a219c8e
521848e
 
 
 
 
 
 
a3b8611
521848e
 
901a06f
521848e
 
 
 
a3b8611
521848e
 
 
 
 
 
 
 
 
 
 
 
a3b8611
9312825
 
 
edc98e3
9312825
 
 
521848e
9312825
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
edc98e3
 
 
cda0e5d
 
 
 
 
 
 
 
 
 
 
edc98e3
0d1e776
 
a3b8611
9312825
edc98e3
9312825
 
 
 
 
 
 
 
 
 
 
 
 
cda0e5d
9312825
 
 
 
 
edc98e3
 
 
 
 
 
 
9312825
 
521848e
9312825
a219c8e
 
9312825
 
a219c8e
 
9312825
 
a219c8e
 
 
9312825
 
 
 
 
 
 
 
 
 
 
a219c8e
9312825
a219c8e
9312825
 
ee37f40
 
 
 
9312825
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
import logging
import gradio as gr
from yt_dlp import YoutubeDL
import os
from dotenv import load_dotenv
from pathlib import Path
import tempfile
import shutil
from pathlib import Path

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        #logging.FileHandler('youtube_downloader.log'),
        logging.StreamHandler()
    ]
)

# Create logger instance
logger = logging.getLogger('youtube_downloader')

def cookies_to_env(cookie_file_path: str) -> str:
    """Convert cookie file content to environment variable format"""
    try:
        with open(cookie_file_path, 'r') as f:
            lines = f.readlines()
            
        # Keep header comments
        header = [line.strip() for line in lines if line.startswith('#')]
        # Get cookie content (non-comment lines)
        cookies = [line.strip() for line in lines if line.strip() and not line.startswith('#')]
        
        # Join with escaped newlines
        content = '\\n'.join(header + [''] + cookies)  # Empty line after headers
        
        # Create env file content
        return f'FIREFOX_COOKIES="{content}"'
    
    except Exception as e:
        raise ValueError(f"Error converting cookie file: {str(e)}")

def env_to_cookies(env_content: str, output_file: str) -> None:
    """Convert environment variable content back to cookie file"""
    try:
        # Extract content from env format
        if '="' not in env_content:
            raise ValueError("Invalid env content format")
            
        content = env_content.split('="', 1)[1].strip('"')
        
        # Replace escaped newlines with actual newlines
        cookie_content = content.replace('\\n', '\n')
        
        # Write to cookie file
        with open(output_file, 'w') as f:
            f.write(cookie_content)
            
    except Exception as e:
        raise ValueError(f"Error converting to cookie file: {str(e)}")

def save_to_env_file(env_content: str, env_file: str = '.env') -> None:
    """Save environment variable content to .env file"""
    try:
        with open(env_file, 'w') as f:
            f.write(env_content)
        #print(f"Successfully saved to {env_file}")
    except Exception as e:
        raise ValueError(f"Error saving to env file: {str(e)}")

def env_to_cookies_from_env(output_file: str) -> None:
    """Convert environment variable from .env file to cookie file"""
    try:
        load_dotenv()  # Load from .env file
        env_content = os.getenv('FIREFOX_COOKIES', "")
        #print(f"Printing env content: \n{env_content}")
        if not env_content:
            raise ValueError("FIREFOX_COOKIES not found in .env file")
            
        env_to_cookies(f'FIREFOX_COOKIES="{env_content}"', output_file)
    except Exception as e:
        raise ValueError(f"Error converting to cookie file: {str(e)}")

def get_cookies():
    """Get cookies from environment variable"""
    load_dotenv()
    cookie_content = os.getenv('FIREFOX_COOKIES', "")
    #print(cookie_content)
    if not cookie_content:
        raise ValueError("FIREFOX_COOKIES environment variable not set")
    return cookie_content

def create_temp_cookie_file():
    """Create temporary cookie file from environment variable"""
    temp_cookie = tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.txt')
    try:
        cookie_content = get_cookies()
        # Replace escaped newlines with actual newlines
        cookie_content = cookie_content.replace('\\n', '\n')
        temp_cookie.write()
        temp_cookie.flush()
        return Path(temp_cookie.name)
    finally:
        temp_cookie.close()

def download_for_browser(url, mode='audio', quality='high'):
    if not url:
        return None, "Please enter a valid URL"
    logger.info(f"Downloading {url} in {mode} mode with {quality} quality")
        
    # Create temporary directory that persists until file is served
    temp_dir = Path(tempfile.mkdtemp())
    
    try:
        # Configure download options
        opts = {
            'format': 'bestaudio/best' if mode == 'audio' else 'bestvideo+bestaudio/best',
            'outtmpl': str(temp_dir / '%(title)s.%(ext)s'),
            'restrictfilenames': True,
            'windowsfilenames': True,
            'quiet': True,
            'no_warnings': True
        }
        
        # Add format-specific options
        if mode == 'audio':
            opts.update({
                'postprocessors': [{
                    'key': 'FFmpegExtractAudio',
                    'preferredcodec': 'mp3',
                    'preferredquality': '320' if quality == 'high' else '192',
                }],
                'prefer_ffmpeg': True,
                'keepvideo': False
            })
        else:
            opts.update({
                'format': 'bestvideo+bestaudio/best' if quality == 'high' else 'best[height<=720]',
                'merge_output_format': 'mp4'
            })

        load_dotenv()
        USE_FIREFOX_COOKIES = os.getenv("USE_FIREFOX_COOKIES", "False")
        if USE_FIREFOX_COOKIES == "True":
            # Convert cookie file to env and save locally
            #cookie_file = "cookies.firefox-private.txt"
            #env_content = cookies_to_env(cookie_file)
            #save_to_env_file(env_content)

            # Convert back to cookie file using .env
            cookiefile = "firefox-cookies.txt"
            env_to_cookies_from_env("firefox-cookies.txt")

                    # Add cookies
            logger.info(f"Using Firefox cookies: {USE_FIREFOX_COOKIES}")
            opts["cookiefile"] =  "firefox-cookies.txt" #create_temp_cookie_file() 
        else:
            opts["no_cookies"] = True # Forces yt-dlp to access YouTube without any authentication

        # Download file
        logger.info(f"Downloading {url} with options: {opts}")
        with YoutubeDL(opts) as ydl:
            info = ydl.extract_info(url, download=True)
            
        # Find downloaded file
        files = list(temp_dir.glob('*'))
        if not files:
            return None, "Download failed - no files found"
            
        download_file = files[0]
        if not download_file.exists():
            return None, f"File not found after download: {download_file.name}"
            
        # Return file while it exists in temp directory
        logger.info(f"Downloaded file: {download_file.name}")
        return str(download_file), f"Successfully converted: {download_file.name}"
        
    except Exception as e:
        if temp_dir.exists():
            shutil.rmtree(temp_dir)
        # Return raw error message without formatting
        error_msg = str(e)
        if "ERROR: [youtube]" in error_msg:
            # Extract everything after "ERROR: [youtube]"
            error_msg = error_msg.split("ERROR: [youtube]")[1].strip()
        logger.error(f"Download error: {error_msg}")
        return None, error_msg

def create_browser_ui():
    
    with gr.Blocks(title="YouTube Downloader", theme=gr.themes.Soft()) as demo:

        gr.Markdown("""## <span style='display: flex; align-items: center; gap: 10px;'><img src='https://cdn-icons-png.flaticon.com/512/1384/1384060.png' width='25'/> <img src='https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Youtube_Music_icon.svg/2048px-Youtube_Music_icon.svg.png' width='25'/>YouTube Downloader</span>""")
        
        with gr.Row():
            with gr.Column(scale=1):
                url_input = gr.Textbox(
                label="YouTube URL",
                placeholder="https://youtube.com/watch?v=...",
                scale=1
                )

                mode_input = gr.Radio(
                    choices=["audio", "video"],
                    value="audio",
                    label="Format"
                )
                quality_input = gr.Radio(
                    choices=["high", "medium"],
                    value="high",
                    label="Quality"
                )
        
                download_button = gr.Button("Convert", variant="primary")

        with gr.Column(scale=1):
            status_text = gr.Textbox(label="Converting Status", interactive=False)
            output_file = gr.File(label="Download to your device ...")
            gr.Markdown("""### ⓘ Note:
                        The downloader currently works only when running locally.<br> 
                        To run it in HF deployed space, cookies are required to access YouTube.<br>
                        For more details, visit [README](https://huggingface.co/spaces/prasanthntu/youtube-downloader/blob/main/README.md)""", label="Note", show_label=True, container=True)

        download_button.click(
            fn=download_for_browser,
            inputs=[url_input, mode_input, quality_input],
            outputs=[output_file, status_text]
        )

    return demo

demo = create_browser_ui()
demo.launch(
    share=False,
    debug=True,
    show_error=True
    )