convertfilling / app.py
shuvo108's picture
Update app.py
71ad0d9 verified
import os
import subprocess
import tempfile
import uuid
from flask import Flask, request, send_file, jsonify, render_template_string
from werkzeug.utils import secure_filename
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
# HTML template for simple UI
HTML_TEMPLATE = '''
<!DOCTYPE html>
<html>
<head>
<title>Word to PDF Converter</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
padding: 40px;
width: 100%;
max-width: 500px;
text-align: center;
}
h1 {
color: #333;
margin-bottom: 10px;
font-size: 28px;
}
.subtitle {
color: #666;
margin-bottom: 30px;
font-size: 16px;
}
.upload-area {
border: 3px dashed #667eea;
border-radius: 15px;
padding: 40px 20px;
margin: 20px 0;
background: #f8f9fa;
cursor: pointer;
transition: all 0.3s;
}
.upload-area:hover {
background: #eef2ff;
border-color: #764ba2;
}
.upload-icon {
font-size: 48px;
color: #667eea;
margin-bottom: 15px;
}
.file-input {
display: none;
}
.file-name {
margin-top: 10px;
color: #666;
font-size: 14px;
word-break: break-all;
}
.convert-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 15px 40px;
font-size: 18px;
border-radius: 50px;
cursor: pointer;
margin-top: 20px;
width: 100%;
transition: transform 0.3s;
}
.convert-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
}
.convert-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.progress-bar {
width: 100%;
height: 6px;
background: #e0e0e0;
border-radius: 3px;
margin: 20px 0;
overflow: hidden;
display: none;
}
.progress {
width: 0%;
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
transition: width 0.3s;
}
.status {
margin: 15px 0;
min-height: 24px;
}
.success { color: #10b981; }
.error { color: #ef4444; }
.info { color: #3b82f6; }
.api-info {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e5e7eb;
text-align: left;
}
.api-info h3 {
color: #333;
margin-bottom: 10px;
font-size: 18px;
}
.api-info code {
background: #f3f4f6;
padding: 2px 6px;
border-radius: 4px;
font-size: 14px;
}
.supported-formats {
display: flex;
justify-content: center;
gap: 10px;
margin: 15px 0;
flex-wrap: wrap;
}
.format-badge {
background: #eef2ff;
color: #667eea;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<h1>📄 Word to PDF Converter</h1>
<p class="subtitle">Convert Word documents to PDF instantly for free</p>
<div class="supported-formats">
<span class="format-badge">.DOC</span>
<span class="format-badge">.DOCX</span>
<span class="format-badge">.TXT</span>
<span class="format-badge">.ODT</span>
</div>
<div class="upload-area" onclick="document.getElementById('fileInput').click()">
<div class="upload-icon">📤</div>
<h3>Click to upload Word file</h3>
<p>or drag and drop</p>
<p style="font-size: 12px; color: #999; margin-top: 10px;">Max size: 50MB</p>
<div class="file-name" id="fileName">No file chosen</div>
</div>
<input type="file" id="fileInput" class="file-input" accept=".doc,.docx,.txt,.odt" onchange="updateFileName()">
<div class="progress-bar" id="progressBar">
<div class="progress" id="progress"></div>
</div>
<div class="status" id="status"></div>
<button class="convert-btn" onclick="convertFile()" id="convertBtn">Convert to PDF</button>
<div class="api-info">
<h3>API Usage:</h3>
<p><strong>Endpoint:</strong> <code>POST /convert</code></p>
<p><strong>Example (curl):</strong></p>
<code>curl -X POST -F "file=@document.docx" {{ url_for('convert_word_to_pdf') }} --output output.pdf</code>
</div>
</div>
<script>
let selectedFile = null;
function updateFileName() {
const fileInput = document.getElementById('fileInput');
const fileNameDiv = document.getElementById('fileName');
if (fileInput.files.length > 0) {
selectedFile = fileInput.files[0];
fileNameDiv.textContent = selectedFile.name + ' (' + formatFileSize(selectedFile.size) + ')';
fileNameDiv.style.color = '#333';
} else {
selectedFile = null;
fileNameDiv.textContent = 'No file chosen';
fileNameDiv.style.color = '#666';
}
}
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' bytes';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
function showStatus(message, type) {
const statusDiv = document.getElementById('status');
statusDiv.innerHTML = `<p class="${type}">${message}</p>`;
statusDiv.style.display = 'block';
}
async function convertFile() {
if (!selectedFile) {
showStatus('Please select a file first!', 'error');
return;
}
if (selectedFile.size > 50 * 1024 * 1024) {
showStatus('File size must be less than 50MB', 'error');
return;
}
const convertBtn = document.getElementById('convertBtn');
const progressBar = document.getElementById('progressBar');
const progress = document.getElementById('progress');
// Reset and show progress
convertBtn.disabled = true;
convertBtn.textContent = 'Converting...';
progressBar.style.display = 'block';
progress.style.width = '30%';
showStatus('Uploading file...', 'info');
const formData = new FormData();
formData.append('file', selectedFile);
try {
progress.style.width = '60%';
showStatus('Converting to PDF...', 'info');
const response = await fetch('/convert', {
method: 'POST',
body: formData
});
progress.style.width = '90%';
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
// Create download link
const a = document.createElement('a');
a.href = url;
a.download = selectedFile.name.replace(/\.[^/.]+$/, "") + '.pdf';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
progress.style.width = '100%';
showStatus('✅ Conversion successful! Download started.', 'success');
// Reset after 3 seconds
setTimeout(() => {
progressBar.style.display = 'none';
progress.style.width = '0%';
showStatus('', '');
}, 3000);
} else {
const error = await response.text();
showStatus('❌ Error: ' + error, 'error');
}
} catch (error) {
showStatus('❌ Network error: ' + error.message, 'error');
} finally {
convertBtn.disabled = false;
convertBtn.textContent = 'Convert to PDF';
setTimeout(() => progress.style.width = '0%', 500);
}
}
// Drag and drop functionality
const uploadArea = document.querySelector('.upload-area');
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.style.background = '#eef2ff';
uploadArea.style.borderColor = '#764ba2';
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.style.background = '#f8f9fa';
uploadArea.style.borderColor = '#667eea';
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.style.background = '#f8f9fa';
uploadArea.style.borderColor = '#667eea';
if (e.dataTransfer.files.length) {
document.getElementById('fileInput').files = e.dataTransfer.files;
updateFileName();
}
});
</script>
</body>
</html>
'''
@app.route('/')
def home():
return render_template_string(HTML_TEMPLATE)
@app.route('/api/help')
def api_help():
return jsonify({
"endpoints": {
"GET /": "Web interface",
"POST /convert": "Convert Word to PDF (returns PDF file)",
"GET /api/help": "This help message",
"GET /api/health": "Health check"
},
"supported_formats": [".doc", ".docx", ".txt", ".odt"],
"max_file_size": "50MB",
"example_curl": "curl -X POST -F 'file=@document.docx' https://shuvo108-convertfilling.hf.space/convert --output output.pdf"
})
@app.route('/api/health')
def health_check():
return jsonify({
"status": "healthy",
"service": "word-to-pdf-converter",
"version": "1.0.0"
})
@app.route('/convert', methods=['POST'])
def convert_word_to_pdf():
if 'file' not in request.files:
return 'No file uploaded', 400
file = request.files['file']
if file.filename == '':
return 'No file selected', 400
# Check file extension
allowed_ext = ['.doc', '.docx', '.txt', '.odt']
if not any(file.filename.lower().endswith(ext) for ext in allowed_ext):
return 'File type not supported. Use: .doc, .docx, .txt, .odt', 400
# Check file size (50MB limit)
file.seek(0, 2) # Seek to end
file_size = file.tell()
file.seek(0) # Reset to beginning
if file_size > 50 * 1024 * 1024:
return 'File too large. Max 50MB', 400
with tempfile.TemporaryDirectory() as tmpdir:
# Save uploaded file
input_path = os.path.join(tmpdir, secure_filename(file.filename))
file.save(input_path)
# Generate output filename
base_name = os.path.splitext(file.filename)[0]
pdf_filename = f"{base_name}_converted.pdf"
output_path = os.path.join(tmpdir, pdf_filename)
try:
# Try LibreOffice first
libreoffice_paths = ['/usr/bin/libreoffice', '/usr/local/bin/libreoffice']
for libreoffice in libreoffice_paths:
if os.path.exists(libreoffice):
cmd = [
libreoffice,
'--headless',
'--convert-to', 'pdf',
'--outdir', tmpdir,
input_path
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode == 0:
# Find the generated PDF
for f in os.listdir(tmpdir):
if f.endswith('.pdf'):
generated_pdf = os.path.join(tmpdir, f)
os.rename(generated_pdf, output_path)
break
if os.path.exists(output_path):
return send_file(
output_path,
as_attachment=True,
download_name=pdf_filename,
mimetype='application/pdf'
)
# Fallback: if LibreOffice failed
return 'Conversion failed. Please try a different file.', 500
except subprocess.TimeoutExpired:
return 'Conversion timeout. File might be too large or complex.', 500
except Exception as e:
return f'Error: {str(e)}', 500
if __name__ == '__main__':
port = int(os.environ.get('PORT', 7860))
app.run(host='0.0.0.0', port=port, debug=False)