Spaces:
Sleeping
Sleeping
Commit ·
016fc9a
1
Parent(s): dfa9d54
cookies
Browse files- Dockerfile +13 -5
- app.py +355 -148
- requirements.txt +3 -1
Dockerfile
CHANGED
|
@@ -6,21 +6,29 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
| 6 |
ffmpeg \
|
| 7 |
build-essential \
|
| 8 |
libsndfile1 \
|
|
|
|
|
|
|
| 9 |
&& apt-get clean \
|
| 10 |
-
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
| 11 |
|
| 12 |
COPY requirements.txt .
|
| 13 |
|
| 14 |
-
RUN pip install --no-cache-dir -r requirements.txt
|
|
|
|
| 15 |
|
| 16 |
COPY . .
|
| 17 |
|
| 18 |
-
RUN mkdir -p /app/static/audio
|
|
|
|
| 19 |
|
| 20 |
EXPOSE 7860
|
| 21 |
|
| 22 |
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 23 |
PYTHONUNBUFFERED=1 \
|
| 24 |
-
MODEL_NAME="google/pegasus-xsum"
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
-
CMD ["gunicorn", "--bind", "0.0.0.0:7860", "app:app"]
|
|
|
|
| 6 |
ffmpeg \
|
| 7 |
build-essential \
|
| 8 |
libsndfile1 \
|
| 9 |
+
ca-certificates \
|
| 10 |
+
openssl \
|
| 11 |
&& apt-get clean \
|
| 12 |
+
&& rm -rf /var/lib/apt/lists/* \
|
| 13 |
+
&& update-ca-certificates
|
| 14 |
|
| 15 |
COPY requirements.txt .
|
| 16 |
|
| 17 |
+
RUN pip install --no-cache-dir -r requirements.txt \
|
| 18 |
+
&& pip install --no-cache-dir pytube
|
| 19 |
|
| 20 |
COPY . .
|
| 21 |
|
| 22 |
+
RUN mkdir -p /app/static/audio /tmp/yt-dlp
|
| 23 |
+
RUN chmod -R 777 /tmp
|
| 24 |
|
| 25 |
EXPOSE 7860
|
| 26 |
|
| 27 |
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 28 |
PYTHONUNBUFFERED=1 \
|
| 29 |
+
MODEL_NAME="google/pegasus-xsum" \
|
| 30 |
+
PYTHONHTTPSVERIFY=0 \
|
| 31 |
+
SSL_CERT_DIR=/etc/ssl/certs \
|
| 32 |
+
REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
|
| 33 |
|
| 34 |
+
CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--timeout", "300", "--workers", "1", "app:app"]
|
app.py
CHANGED
|
@@ -16,17 +16,47 @@ import tempfile
|
|
| 16 |
|
| 17 |
load_dotenv()
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
#
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
app = Flask(__name__)
|
| 32 |
|
|
@@ -61,25 +91,78 @@ def load_pegasus_model():
|
|
| 61 |
return tokenizer, model
|
| 62 |
|
| 63 |
|
| 64 |
-
def transcribe_audio_with_whisper(audio_data, timeout=
|
| 65 |
try:
|
| 66 |
logging.info("Transcribing audio data")
|
| 67 |
start_time = time.time()
|
| 68 |
model = load_whisper_model()
|
| 69 |
|
| 70 |
-
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=
|
| 71 |
if isinstance(audio_data, io.BytesIO):
|
| 72 |
temp_file.write(audio_data.getvalue())
|
| 73 |
else:
|
| 74 |
temp_file.write(audio_data)
|
| 75 |
temp_file.flush()
|
|
|
|
| 76 |
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
return result["text"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
except Exception as e:
|
| 84 |
logging.error(f"Error in audio transcription: {e}")
|
| 85 |
raise ValueError(f"Error in audio transcription: {e}")
|
|
@@ -113,146 +196,209 @@ def summarize_text_with_pegasus(text, tokenizer, model):
|
|
| 113 |
raise ValueError(f"Error in text summarization: {e}")
|
| 114 |
|
| 115 |
|
| 116 |
-
def
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
# Create a temp directory for cookies with correct permissions
|
| 120 |
-
cookie_dir = tempfile.mkdtemp()
|
| 121 |
-
cookies_path = os.path.join(cookie_dir, "cookies.txt")
|
| 122 |
-
|
| 123 |
-
# Create an empty cookies file with appropriate permissions
|
| 124 |
-
with open(cookies_path, "w") as f:
|
| 125 |
-
f.write("")
|
| 126 |
-
os.chmod(cookies_path, 0o666)
|
| 127 |
-
|
| 128 |
-
ydl_opts = {
|
| 129 |
-
"format": "bestaudio/best",
|
| 130 |
-
"postprocessors": [
|
| 131 |
-
{
|
| 132 |
-
"key": "FFmpegExtractAudio",
|
| 133 |
-
"preferredcodec": "mp3",
|
| 134 |
-
"preferredquality": "192",
|
| 135 |
-
}
|
| 136 |
-
],
|
| 137 |
-
"outtmpl": "-",
|
| 138 |
-
"logtostderr": True,
|
| 139 |
-
"quiet": False,
|
| 140 |
-
"no_warnings": False,
|
| 141 |
-
"extract_audio": True,
|
| 142 |
-
"nocheckcertificate": True,
|
| 143 |
-
"ignoreerrors": True,
|
| 144 |
-
"no_color": True,
|
| 145 |
-
"geo_bypass": True,
|
| 146 |
-
"cookies": cookies_path, # Use our temp cookies file
|
| 147 |
-
"socket_timeout": 30,
|
| 148 |
-
"retries": 3,
|
| 149 |
-
}
|
| 150 |
-
|
| 151 |
try:
|
| 152 |
-
logging.info(f"Downloading
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
|
| 154 |
-
|
| 155 |
-
cert_path = os.path.join(tempfile.gettempdir(), "cacert.pem")
|
| 156 |
-
import certifi
|
| 157 |
|
| 158 |
-
|
| 159 |
-
|
|
|
|
| 160 |
|
| 161 |
-
os.environ["SSL_CERT_FILE"] = cert_path
|
| 162 |
-
os.environ["REQUESTS_CA_BUNDLE"] = cert_path
|
| 163 |
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
|
|
|
|
| 169 |
try:
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
".m4a", ext
|
| 185 |
-
)
|
| 186 |
-
if os.path.exists(possible_path):
|
| 187 |
-
audio_file_path = possible_path
|
| 188 |
-
break
|
| 189 |
-
|
| 190 |
-
logging.info(f"Audio downloaded to: {audio_file_path}")
|
| 191 |
-
|
| 192 |
-
if not os.path.exists(audio_file_path):
|
| 193 |
-
raise ValueError(
|
| 194 |
-
f"Downloaded file {audio_file_path} does not exist"
|
| 195 |
-
)
|
| 196 |
-
|
| 197 |
-
# Read the file and clean up
|
| 198 |
-
with open(audio_file_path, "rb") as audio_file:
|
| 199 |
-
buffer = io.BytesIO(audio_file.read())
|
| 200 |
-
buffer.seek(0)
|
| 201 |
-
|
| 202 |
-
# Remove the temporary file
|
| 203 |
-
try:
|
| 204 |
-
os.unlink(audio_file_path)
|
| 205 |
-
except Exception as cleanup_err:
|
| 206 |
-
logging.warning(f"Could not remove temp file: {cleanup_err}")
|
| 207 |
-
except Exception as ydl_err:
|
| 208 |
-
logging.error(f"YoutubeDL error: {ydl_err}")
|
| 209 |
-
# Try alternative download method - direct URL only, no fancy features
|
| 210 |
-
logging.info("Attempting alternative download method...")
|
| 211 |
-
|
| 212 |
-
simple_opts = {
|
| 213 |
-
"format": "bestaudio",
|
| 214 |
-
"outtmpl": output_path,
|
| 215 |
-
"nocheckcertificate": True,
|
| 216 |
-
"quiet": True,
|
| 217 |
-
"no_warnings": True,
|
| 218 |
-
"geo_bypass": True,
|
| 219 |
-
}
|
| 220 |
|
| 221 |
-
|
| 222 |
-
info = ydl.extract_info(url, download=True)
|
| 223 |
-
if not info:
|
| 224 |
-
raise ValueError("Could not fetch video information")
|
| 225 |
|
| 226 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
f"Downloaded file {audio_file_path} does not exist"
|
| 231 |
-
)
|
| 232 |
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
buffer = io.BytesIO(audio_file.read())
|
| 236 |
-
buffer.seek(0)
|
| 237 |
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
buffer = convert_audio_to_mp3(
|
| 241 |
-
buffer.getvalue(),
|
| 242 |
-
original_format=audio_file_path.split(".")[-1],
|
| 243 |
-
)
|
| 244 |
|
| 245 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
try:
|
| 247 |
-
os.unlink(
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
pass
|
| 251 |
|
| 252 |
return buffer
|
|
|
|
| 253 |
except Exception as e:
|
| 254 |
-
logging.error(f"
|
| 255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
|
| 257 |
|
| 258 |
def allowed_file(filename):
|
|
@@ -281,56 +427,117 @@ def index():
|
|
| 281 |
|
| 282 |
@app.route("/transcribe", methods=["POST"])
|
| 283 |
def transcribe():
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
try:
|
| 285 |
audio_data = None
|
| 286 |
|
| 287 |
if "url" in request.form and request.form["url"]:
|
| 288 |
-
youtube_url = request.form["url"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
try:
|
| 290 |
audio_data = download_audio_from_youtube(youtube_url)
|
| 291 |
-
|
| 292 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
return (
|
| 294 |
jsonify(
|
| 295 |
{
|
| 296 |
-
"error": "YouTube
|
| 297 |
}
|
| 298 |
),
|
| 299 |
400,
|
| 300 |
)
|
| 301 |
else:
|
| 302 |
raise e
|
|
|
|
| 303 |
elif "file" in request.files:
|
| 304 |
audio_file = request.files["file"]
|
| 305 |
if not audio_file.filename:
|
| 306 |
return jsonify({"error": "No file selected."}), 400
|
|
|
|
| 307 |
if not allowed_file(audio_file.filename):
|
| 308 |
return (
|
| 309 |
jsonify(
|
| 310 |
-
{
|
|
|
|
|
|
|
| 311 |
),
|
| 312 |
400,
|
| 313 |
)
|
| 314 |
|
| 315 |
audio_bytes = audio_file.read()
|
| 316 |
file_format = audio_file.filename.rsplit(".", 1)[1].lower()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
audio_data = convert_audio_to_mp3(audio_bytes, original_format=file_format)
|
|
|
|
|
|
|
|
|
|
| 318 |
else:
|
| 319 |
return jsonify({"error": "No audio file or URL provided."}), 400
|
| 320 |
|
|
|
|
|
|
|
| 321 |
transcription = transcribe_audio_with_whisper(audio_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
|
| 323 |
if transcription:
|
|
|
|
|
|
|
| 324 |
tokenizer, model = load_pegasus_model()
|
| 325 |
summary = summarize_text_with_pegasus(transcription, tokenizer, model)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
return jsonify({"transcription": transcription, "summary": summary})
|
| 327 |
else:
|
| 328 |
-
return jsonify({"error": "Transcription failed."}), 500
|
|
|
|
| 329 |
except ValueError as e:
|
|
|
|
| 330 |
return jsonify({"error": str(e)}), 400
|
| 331 |
except Exception as e:
|
| 332 |
-
logging.error(f"An unexpected error occurred: {e}")
|
| 333 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
|
| 335 |
|
| 336 |
if __name__ == "__main__":
|
|
|
|
| 16 |
|
| 17 |
load_dotenv()
|
| 18 |
|
| 19 |
+
|
| 20 |
+
def setup_environment():
|
| 21 |
+
"""Configure environment for Hugging Face Spaces"""
|
| 22 |
+
# Create directories with proper permissions
|
| 23 |
+
for directory in [
|
| 24 |
+
"/tmp/transformers_cache",
|
| 25 |
+
"/tmp/hf_home",
|
| 26 |
+
"/tmp/cache",
|
| 27 |
+
"/tmp/yt-dlp",
|
| 28 |
+
"/tmp/certs",
|
| 29 |
+
]:
|
| 30 |
+
os.makedirs(directory, exist_ok=True)
|
| 31 |
+
try:
|
| 32 |
+
# Ensure the directory is writeable
|
| 33 |
+
os.chmod(directory, 0o777)
|
| 34 |
+
except Exception as e:
|
| 35 |
+
logging.warning(f"Could not set permissions on {directory}: {e}")
|
| 36 |
+
|
| 37 |
+
# Set environment variables
|
| 38 |
+
os.environ["TRANSFORMERS_CACHE"] = "/tmp/transformers_cache"
|
| 39 |
+
os.environ["HF_HOME"] = "/tmp/hf_home"
|
| 40 |
+
os.environ["XDG_CACHE_HOME"] = "/tmp/cache"
|
| 41 |
+
|
| 42 |
+
# Certificate handling
|
| 43 |
+
os.environ["PYTHONHTTPSVERIFY"] = "0"
|
| 44 |
+
os.environ["REQUESTS_CA_BUNDLE"] = "/etc/ssl/certs/ca-certificates.crt"
|
| 45 |
+
os.environ["SSL_CERT_DIR"] = "/etc/ssl/certs"
|
| 46 |
+
|
| 47 |
+
# Set this to the temp directory to avoid permission issues
|
| 48 |
+
os.environ["HOME"] = "/tmp"
|
| 49 |
+
|
| 50 |
+
# For yt-dlp
|
| 51 |
+
os.environ["no_proxy"] = "*"
|
| 52 |
+
|
| 53 |
+
# Disable warnings that might flood logs
|
| 54 |
+
import warnings
|
| 55 |
+
|
| 56 |
+
warnings.filterwarnings("ignore", category=UserWarning)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
setup_environment()
|
| 60 |
|
| 61 |
app = Flask(__name__)
|
| 62 |
|
|
|
|
| 91 |
return tokenizer, model
|
| 92 |
|
| 93 |
|
| 94 |
+
def transcribe_audio_with_whisper(audio_data, timeout=300): # 5 minute timeout
|
| 95 |
try:
|
| 96 |
logging.info("Transcribing audio data")
|
| 97 |
start_time = time.time()
|
| 98 |
model = load_whisper_model()
|
| 99 |
|
| 100 |
+
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_file:
|
| 101 |
if isinstance(audio_data, io.BytesIO):
|
| 102 |
temp_file.write(audio_data.getvalue())
|
| 103 |
else:
|
| 104 |
temp_file.write(audio_data)
|
| 105 |
temp_file.flush()
|
| 106 |
+
temp_file_path = temp_file.name
|
| 107 |
|
| 108 |
+
try:
|
| 109 |
+
# Use multiprocessing to implement a timeout
|
| 110 |
+
from multiprocessing import Process, Queue
|
| 111 |
+
|
| 112 |
+
def transcribe_process(file_path, result_queue):
|
| 113 |
+
try:
|
| 114 |
+
model = load_whisper_model()
|
| 115 |
+
result = model.transcribe(file_path)
|
| 116 |
+
result_queue.put(result)
|
| 117 |
+
except Exception as e:
|
| 118 |
+
result_queue.put(e)
|
| 119 |
+
|
| 120 |
+
# Create a queue for the result
|
| 121 |
+
result_queue = Queue()
|
| 122 |
+
# Create and start the process
|
| 123 |
+
process = Process(
|
| 124 |
+
target=transcribe_process, args=(temp_file_path, result_queue)
|
| 125 |
+
)
|
| 126 |
+
process.start()
|
| 127 |
+
|
| 128 |
+
# Wait for the specified timeout
|
| 129 |
+
process.join(timeout)
|
| 130 |
+
|
| 131 |
+
# If process is still running after timeout, terminate it
|
| 132 |
+
if process.is_alive():
|
| 133 |
+
process.terminate()
|
| 134 |
+
process.join()
|
| 135 |
+
os.unlink(temp_file_path) # Clean up
|
| 136 |
+
raise TimeoutError(f"Transcription timed out after {timeout} seconds")
|
| 137 |
+
|
| 138 |
+
# Get the result
|
| 139 |
+
if result_queue.empty():
|
| 140 |
+
os.unlink(temp_file_path) # Clean up
|
| 141 |
+
raise ValueError("Transcription process failed")
|
| 142 |
+
|
| 143 |
+
result_or_error = result_queue.get()
|
| 144 |
+
if isinstance(result_or_error, Exception):
|
| 145 |
+
os.unlink(temp_file_path) # Clean up
|
| 146 |
+
raise result_or_error
|
| 147 |
+
|
| 148 |
+
result = result_or_error
|
| 149 |
+
|
| 150 |
+
finally:
|
| 151 |
+
# Clean up temp file
|
| 152 |
+
try:
|
| 153 |
+
os.unlink(temp_file_path)
|
| 154 |
+
except:
|
| 155 |
+
pass
|
| 156 |
+
|
| 157 |
+
elapsed = time.time() - start_time
|
| 158 |
+
logging.info(f"Transcription completed in {elapsed:.2f} seconds")
|
| 159 |
|
| 160 |
return result["text"]
|
| 161 |
+
except TimeoutError as e:
|
| 162 |
+
logging.error(f"Transcription timeout: {e}")
|
| 163 |
+
raise ValueError(
|
| 164 |
+
"Audio transcription took too long. Please try a shorter audio file."
|
| 165 |
+
)
|
| 166 |
except Exception as e:
|
| 167 |
logging.error(f"Error in audio transcription: {e}")
|
| 168 |
raise ValueError(f"Error in audio transcription: {e}")
|
|
|
|
| 196 |
raise ValueError(f"Error in text summarization: {e}")
|
| 197 |
|
| 198 |
|
| 199 |
+
def download_youtube_with_cookies(url):
|
| 200 |
+
"""Download YouTube audio using the project's cookies file"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
try:
|
| 202 |
+
logging.info(f"Downloading YouTube with cookies: {url}")
|
| 203 |
+
# Use the cookies.txt from the project directory
|
| 204 |
+
cookies_path = os.path.join(os.getcwd(), "cookies.txt")
|
| 205 |
+
|
| 206 |
+
if not os.path.exists(cookies_path):
|
| 207 |
+
logging.warning("cookies.txt not found in project directory")
|
| 208 |
+
# Create an empty cookies file
|
| 209 |
+
with open(cookies_path, "w") as f:
|
| 210 |
+
f.write("# Netscape HTTP Cookie File\n")
|
| 211 |
+
|
| 212 |
+
logging.info(f"Using cookies from: {cookies_path}")
|
| 213 |
+
|
| 214 |
+
output_dir = "/tmp/yt_downloads"
|
| 215 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 216 |
+
os.chmod(output_dir, 0o777)
|
| 217 |
+
|
| 218 |
+
output_path = os.path.join(output_dir, f"download_{int(time.time())}.%(ext)s")
|
| 219 |
+
|
| 220 |
+
ydl_opts = {
|
| 221 |
+
"format": "bestaudio/best",
|
| 222 |
+
"postprocessors": [
|
| 223 |
+
{
|
| 224 |
+
"key": "FFmpegExtractAudio",
|
| 225 |
+
"preferredcodec": "mp3",
|
| 226 |
+
"preferredquality": "192",
|
| 227 |
+
}
|
| 228 |
+
],
|
| 229 |
+
"outtmpl": output_path,
|
| 230 |
+
"cookies": cookies_path,
|
| 231 |
+
"nocheckcertificate": True,
|
| 232 |
+
"ignoreerrors": True,
|
| 233 |
+
"geo_bypass": True,
|
| 234 |
+
"logtostderr": True,
|
| 235 |
+
"quiet": False,
|
| 236 |
+
"no_warnings": False,
|
| 237 |
+
"socket_timeout": 30,
|
| 238 |
+
"retries": 5,
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
with YoutubeDL(ydl_opts) as ydl:
|
| 242 |
+
info = ydl.extract_info(url, download=True)
|
| 243 |
+
if not info:
|
| 244 |
+
raise ValueError("Could not fetch video information")
|
| 245 |
+
|
| 246 |
+
filename = ydl.prepare_filename(info)
|
| 247 |
+
# Handle potential mp3 extension
|
| 248 |
+
if not filename.endswith(".mp3"):
|
| 249 |
+
filename = filename.rsplit(".", 1)[0] + ".mp3"
|
| 250 |
+
|
| 251 |
+
if not os.path.exists(filename):
|
| 252 |
+
# Try alternative extensions
|
| 253 |
+
for ext in [".mp3", ".webm.mp3", ".m4a.mp3"]:
|
| 254 |
+
alt_filename = filename.rsplit(".", 1)[0] + ext
|
| 255 |
+
if os.path.exists(alt_filename):
|
| 256 |
+
filename = alt_filename
|
| 257 |
+
break
|
| 258 |
+
|
| 259 |
+
logging.info(f"Downloaded file: {filename}")
|
| 260 |
+
|
| 261 |
+
if not os.path.exists(filename):
|
| 262 |
+
raise FileNotFoundError(f"Could not find downloaded file: {filename}")
|
| 263 |
+
|
| 264 |
+
with open(filename, "rb") as f:
|
| 265 |
+
buffer = io.BytesIO(f.read())
|
| 266 |
+
buffer.seek(0)
|
| 267 |
+
|
| 268 |
+
# Clean up
|
| 269 |
+
try:
|
| 270 |
+
os.unlink(filename)
|
| 271 |
+
except Exception as e:
|
| 272 |
+
logging.warning(f"Could not remove temp file: {e}")
|
| 273 |
|
| 274 |
+
return buffer
|
|
|
|
|
|
|
| 275 |
|
| 276 |
+
except Exception as e:
|
| 277 |
+
logging.error(f"Error downloading with cookies: {e}", exc_info=True)
|
| 278 |
+
raise ValueError(f"Error downloading with cookies: {e}")
|
| 279 |
|
|
|
|
|
|
|
| 280 |
|
| 281 |
+
def download_youtube_direct(url):
|
| 282 |
+
"""Direct YouTube download without cookies, simplified options"""
|
| 283 |
+
try:
|
| 284 |
+
logging.info(f"Attempting direct YouTube download: {url}")
|
| 285 |
+
|
| 286 |
+
output_dir = "/tmp/yt_direct"
|
| 287 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 288 |
+
os.chmod(output_dir, 0o777)
|
| 289 |
+
|
| 290 |
+
output_path = os.path.join(output_dir, f"direct_{int(time.time())}.%(ext)s")
|
| 291 |
+
|
| 292 |
+
ydl_opts = {
|
| 293 |
+
"format": "bestaudio",
|
| 294 |
+
"outtmpl": output_path,
|
| 295 |
+
"nocheckcertificate": True,
|
| 296 |
+
"ignoreerrors": False,
|
| 297 |
+
"geo_bypass": True,
|
| 298 |
+
"no_warnings": True,
|
| 299 |
+
"quiet": True,
|
| 300 |
+
"skip_download": False,
|
| 301 |
+
"noprogress": True,
|
| 302 |
+
"nooverwrites": False,
|
| 303 |
+
"socket_timeout": 30,
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
with YoutubeDL(ydl_opts) as ydl:
|
| 307 |
+
info = ydl.extract_info(url, download=True)
|
| 308 |
+
if not info:
|
| 309 |
+
raise ValueError("Could not fetch video information")
|
| 310 |
+
|
| 311 |
+
filename = ydl.prepare_filename(info)
|
| 312 |
+
|
| 313 |
+
if not os.path.exists(filename):
|
| 314 |
+
raise FileNotFoundError(f"Could not find downloaded file: {filename}")
|
| 315 |
+
|
| 316 |
+
with open(filename, "rb") as f:
|
| 317 |
+
data = f.read()
|
| 318 |
+
|
| 319 |
+
# Convert to mp3 if needed
|
| 320 |
+
if not filename.endswith(".mp3"):
|
| 321 |
+
buffer = convert_audio_to_mp3(
|
| 322 |
+
data, original_format=filename.split(".")[-1]
|
| 323 |
+
)
|
| 324 |
+
else:
|
| 325 |
+
buffer = io.BytesIO(data)
|
| 326 |
+
buffer.seek(0)
|
| 327 |
|
| 328 |
+
# Clean up
|
| 329 |
try:
|
| 330 |
+
os.unlink(filename)
|
| 331 |
+
except Exception as e:
|
| 332 |
+
logging.warning(f"Could not remove temp file: {e}")
|
| 333 |
+
|
| 334 |
+
return buffer
|
| 335 |
+
|
| 336 |
+
except Exception as e:
|
| 337 |
+
logging.error(f"Error in direct download: {e}", exc_info=True)
|
| 338 |
+
raise ValueError(f"Error in direct download: {e}")
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
def download_audio_from_youtube(url):
|
| 342 |
+
"""Main YouTube download function with multiple fallback methods"""
|
| 343 |
+
logging.info(f"Starting YouTube download process for: {url}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
|
| 345 |
+
errors = []
|
|
|
|
|
|
|
|
|
|
| 346 |
|
| 347 |
+
# Method 1: Try with project cookies
|
| 348 |
+
try:
|
| 349 |
+
return download_youtube_with_cookies(url)
|
| 350 |
+
except Exception as e:
|
| 351 |
+
logging.warning(f"Cookie download failed: {e}")
|
| 352 |
+
errors.append(f"Cookie method: {str(e)}")
|
| 353 |
+
|
| 354 |
+
# Method 2: Try direct download
|
| 355 |
+
try:
|
| 356 |
+
return download_youtube_direct(url)
|
| 357 |
+
except Exception as e:
|
| 358 |
+
logging.warning(f"Direct download failed: {e}")
|
| 359 |
+
errors.append(f"Direct method: {str(e)}")
|
| 360 |
+
|
| 361 |
+
# Method 3: Try with pytube as last resort
|
| 362 |
+
try:
|
| 363 |
+
logging.info("Attempting download with pytube")
|
| 364 |
+
from pytube import YouTube
|
| 365 |
+
|
| 366 |
+
yt = YouTube(url)
|
| 367 |
+
stream = yt.streams.filter(only_audio=True).first()
|
| 368 |
|
| 369 |
+
if not stream:
|
| 370 |
+
raise ValueError("No audio stream found")
|
|
|
|
|
|
|
| 371 |
|
| 372 |
+
output_dir = "/tmp/pytube_downloads"
|
| 373 |
+
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
|
|
|
| 374 |
|
| 375 |
+
output_path = stream.download(output_path=output_dir)
|
| 376 |
+
logging.info(f"Downloaded to: {output_path}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
|
| 378 |
+
with open(output_path, "rb") as f:
|
| 379 |
+
data = f.read()
|
| 380 |
+
|
| 381 |
+
# Convert to mp3
|
| 382 |
+
buffer = convert_audio_to_mp3(data, original_format=output_path.split(".")[-1])
|
| 383 |
+
|
| 384 |
+
# Clean up
|
| 385 |
try:
|
| 386 |
+
os.unlink(output_path)
|
| 387 |
+
except Exception as e:
|
| 388 |
+
logging.warning(f"Could not remove pytube temp file: {e}")
|
|
|
|
| 389 |
|
| 390 |
return buffer
|
| 391 |
+
|
| 392 |
except Exception as e:
|
| 393 |
+
logging.error(f"Pytube download failed: {e}")
|
| 394 |
+
errors.append(f"Pytube method: {str(e)}")
|
| 395 |
+
|
| 396 |
+
# All methods failed
|
| 397 |
+
error_message = "All download methods failed:\n" + "\n".join(errors)
|
| 398 |
+
logging.error(error_message)
|
| 399 |
+
raise ValueError(
|
| 400 |
+
"Could not download YouTube audio. Please try uploading an audio file directly or use a different URL."
|
| 401 |
+
)
|
| 402 |
|
| 403 |
|
| 404 |
def allowed_file(filename):
|
|
|
|
| 427 |
|
| 428 |
@app.route("/transcribe", methods=["POST"])
|
| 429 |
def transcribe():
|
| 430 |
+
# Record the start time
|
| 431 |
+
start_time = time.time()
|
| 432 |
+
logging.info("Starting new transcription request")
|
| 433 |
+
|
| 434 |
try:
|
| 435 |
audio_data = None
|
| 436 |
|
| 437 |
if "url" in request.form and request.form["url"]:
|
| 438 |
+
youtube_url = request.form["url"].strip()
|
| 439 |
+
logging.info(f"Processing YouTube URL: {youtube_url}")
|
| 440 |
+
|
| 441 |
+
if not youtube_url.startswith(("http://", "https://")):
|
| 442 |
+
return (
|
| 443 |
+
jsonify(
|
| 444 |
+
{"error": "Invalid URL format. Please provide a complete URL."}
|
| 445 |
+
),
|
| 446 |
+
400,
|
| 447 |
+
)
|
| 448 |
+
|
| 449 |
try:
|
| 450 |
audio_data = download_audio_from_youtube(youtube_url)
|
| 451 |
+
logging.info(
|
| 452 |
+
f"YouTube download completed in {time.time() - start_time:.2f} seconds"
|
| 453 |
+
)
|
| 454 |
+
except Exception as e:
|
| 455 |
+
error_msg = str(e).lower()
|
| 456 |
+
if any(
|
| 457 |
+
term in error_msg
|
| 458 |
+
for term in [
|
| 459 |
+
"bot",
|
| 460 |
+
"sign in",
|
| 461 |
+
"cookie",
|
| 462 |
+
"certificate",
|
| 463 |
+
"permission",
|
| 464 |
+
]
|
| 465 |
+
):
|
| 466 |
return (
|
| 467 |
jsonify(
|
| 468 |
{
|
| 469 |
+
"error": "YouTube access issue. Please try uploading an audio file directly or use a different YouTube URL."
|
| 470 |
}
|
| 471 |
),
|
| 472 |
400,
|
| 473 |
)
|
| 474 |
else:
|
| 475 |
raise e
|
| 476 |
+
|
| 477 |
elif "file" in request.files:
|
| 478 |
audio_file = request.files["file"]
|
| 479 |
if not audio_file.filename:
|
| 480 |
return jsonify({"error": "No file selected."}), 400
|
| 481 |
+
|
| 482 |
if not allowed_file(audio_file.filename):
|
| 483 |
return (
|
| 484 |
jsonify(
|
| 485 |
+
{
|
| 486 |
+
"error": "Invalid file type. Please upload an audio file (mp3, aac, flac, or m4a)."
|
| 487 |
+
}
|
| 488 |
),
|
| 489 |
400,
|
| 490 |
)
|
| 491 |
|
| 492 |
audio_bytes = audio_file.read()
|
| 493 |
file_format = audio_file.filename.rsplit(".", 1)[1].lower()
|
| 494 |
+
logging.info(
|
| 495 |
+
f"Processing uploaded file: {audio_file.filename}, format: {file_format}, size: {len(audio_bytes)} bytes"
|
| 496 |
+
)
|
| 497 |
+
|
| 498 |
audio_data = convert_audio_to_mp3(audio_bytes, original_format=file_format)
|
| 499 |
+
logging.info(
|
| 500 |
+
f"File conversion completed in {time.time() - start_time:.2f} seconds"
|
| 501 |
+
)
|
| 502 |
else:
|
| 503 |
return jsonify({"error": "No audio file or URL provided."}), 400
|
| 504 |
|
| 505 |
+
# Transcribe the audio
|
| 506 |
+
transcribe_start = time.time()
|
| 507 |
transcription = transcribe_audio_with_whisper(audio_data)
|
| 508 |
+
transcribe_time = time.time() - transcribe_start
|
| 509 |
+
logging.info(
|
| 510 |
+
f"Transcription completed in {transcribe_time:.2f} seconds. Text length: {len(transcription)}"
|
| 511 |
+
)
|
| 512 |
|
| 513 |
if transcription:
|
| 514 |
+
# Summarize the transcription
|
| 515 |
+
summary_start = time.time()
|
| 516 |
tokenizer, model = load_pegasus_model()
|
| 517 |
summary = summarize_text_with_pegasus(transcription, tokenizer, model)
|
| 518 |
+
summary_time = time.time() - summary_start
|
| 519 |
+
logging.info(
|
| 520 |
+
f"Summarization completed in {summary_time:.2f} seconds. Summary length: {len(summary)}"
|
| 521 |
+
)
|
| 522 |
+
|
| 523 |
+
total_time = time.time() - start_time
|
| 524 |
+
logging.info(f"Total request completed in {total_time:.2f} seconds")
|
| 525 |
+
|
| 526 |
return jsonify({"transcription": transcription, "summary": summary})
|
| 527 |
else:
|
| 528 |
+
return jsonify({"error": "Transcription failed to produce any text."}), 500
|
| 529 |
+
|
| 530 |
except ValueError as e:
|
| 531 |
+
logging.error(f"ValueError: {str(e)}")
|
| 532 |
return jsonify({"error": str(e)}), 400
|
| 533 |
except Exception as e:
|
| 534 |
+
logging.error(f"An unexpected error occurred: {e}", exc_info=True)
|
| 535 |
+
return (
|
| 536 |
+
jsonify(
|
| 537 |
+
{"error": "An unexpected error occurred while processing your request."}
|
| 538 |
+
),
|
| 539 |
+
500,
|
| 540 |
+
)
|
| 541 |
|
| 542 |
|
| 543 |
if __name__ == "__main__":
|
requirements.txt
CHANGED
|
@@ -7,4 +7,6 @@ python-dotenv==1.0.0
|
|
| 7 |
torch==2.1.1
|
| 8 |
gunicorn==21.2.0
|
| 9 |
numpy==1.24.2
|
| 10 |
-
certifi>=2023.7.22
|
|
|
|
|
|
|
|
|
| 7 |
torch==2.1.1
|
| 8 |
gunicorn==21.2.0
|
| 9 |
numpy==1.24.2
|
| 10 |
+
certifi>=2023.7.22
|
| 11 |
+
pytube>=12.1.0
|
| 12 |
+
werkzeug>=2.2.3
|