kacapower commited on
Commit
10a3aa6
·
verified ·
1 Parent(s): ca4c4ac

Upload 5 files

Browse files
Files changed (4) hide show
  1. Dockerfile +26 -0
  2. README.md +3 -2
  3. main.py +116 -0
  4. requirements.txt +3 -0
Dockerfile ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use a lightweight Debian-based Python image
2
+ FROM python:3.10-slim
3
+
4
+ # Install the four core conversion engines
5
+ RUN apt-get update && apt-get install -y \
6
+ ffmpeg \
7
+ libreoffice \
8
+ imagemagick \
9
+ pandoc \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ # Set the working directory
13
+ WORKDIR /app
14
+
15
+ # Copy and install Python dependencies
16
+ COPY requirements.txt .
17
+ RUN pip install --no-cache-dir -r requirements.txt
18
+
19
+ # Copy the API code
20
+ COPY . .
21
+
22
+ # Expose the mandatory port for Hugging Face Spaces
23
+ EXPOSE 7860
24
+
25
+ # Start the FastAPI server
26
+ CMD ["python", "main.py"]
README.md CHANGED
@@ -1,10 +1,11 @@
1
  ---
2
  title: Backend Filehandler
3
- emoji: 💻
4
- colorFrom: green
5
  colorTo: gray
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: Backend Filehandler
3
+ emoji: 🌍
4
+ colorFrom: gray
5
  colorTo: gray
6
  sdk: docker
7
  pinned: false
8
+ short_description: 'Backend '
9
  ---
10
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
main.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import subprocess
3
+ import shutil
4
+ import tempfile
5
+ from fastapi import FastAPI, UploadFile, File, Form, HTTPException, Security, Depends
6
+ from fastapi.responses import FileResponse
7
+ from fastapi.security.api_key import APIKeyHeader
8
+ from tempfile import NamedTemporaryFile
9
+ import uvicorn
10
+
11
+ app = FastAPI(title="Universal File Converter API - SECURED")
12
+
13
+ # =========================================================
14
+ # BRIDGE CONTROL (SECURITY)
15
+ # =========================================================
16
+ # Define the header name the backend will look for
17
+ API_KEY_NAME = "X-Bridge-Key"
18
+ api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
19
+
20
+ # This key must match the frontend exactly
21
+ API_SECRET_KEY = os.environ.get("BRIDGE_SECRET_KEY", "my_secure_bridge_token_2026")
22
+
23
+ async def get_api_key(api_key_header: str = Security(api_key_header)):
24
+ """Verifies the Bridge Control badge."""
25
+ if api_key_header == API_SECRET_KEY:
26
+ return api_key_header
27
+ raise HTTPException(
28
+ status_code=403,
29
+ detail="Bridge Control: Access Denied. Unauthorized connection attempt."
30
+ )
31
+
32
+ # =========================================================
33
+ # THE 300+ FORMAT ROUTING DICTIONARY
34
+ # =========================================================
35
+ CONVERSION_ENGINES = {
36
+ **{ext: 'imagemagick' for ext in['jpg', 'jpeg', 'png', 'webp', 'gif', 'bmp', 'tiff', 'ico', 'svg', 'heic', 'eps', 'ps', 'tga', 'jp2', 'pcx', 'pnm', 'pgm', 'ppm', 'pbm', 'xbm', 'xpm', 'cr2', 'crw', 'nef', 'arw', 'dng', 'orf', 'raf', 'rw2', 'pef', 'srw', 'exr', 'hdr', 'dds', 'avif', 'jxl', 'dib', 'jpe', 'tpf', 'vtf', 'wmf', 'emf', 'cdr', 'cpt', 'svgz', 'cgm', 'djvu', 'pcd', 'pict', 'raw', 'xcf']},
37
+ **{ext: 'ffmpeg' for ext in['mp4', 'avi', 'mkv', 'webm', 'mov', 'flv', 'wmv', 'm4v', 'mpeg', '3gp', 'ogv', 'asf', 'rm', 'rmvb', 'ts', 'm2ts', 'vob', 'mxf', 'roq', 'nsv', 'f4v', 'f4p', 'f4a', 'f4b', 'drc', 'yuv', 'nut', 'amv', 'svt', 'ivf', 'mpe', 'mp2v', 'm2v', 'm4p', 'mpg', 'wtv', 'gxf', 'mjpg', 'mts', 'xvid', 'divx', 'mp3', 'wav', 'flac', 'ogg', 'aac', 'm4a', 'wma', 'aiff', 'opus', 'alac', 'amr', 'au', 'snd', 'mid', 'midi', 'kar', 'mka', 'mac', 'ape', 'spx', 'rka', 'shn', 'tta', 'smp', 'dts', 'pcm', 'gsm', 'dct', 'voc', 'aup', 'band', 'bwf', 'cda', 'ds2', 'dss', 'dvf', 'm4r', 'm4b', 'mus', 'qcp', 'ra', 'ram', 'rx2', 's3m', 'srm', 'uax', 'xm', 'it', 'ac3', 'caf']},
38
+ **{ext: 'libreoffice' for ext in['pdf', 'docx', 'doc', 'odt', 'rtf', 'wpd', 'wps', 'sdw', 'sgl', 'lwp', 'docm', 'dot', 'dotx', 'dotm', 'pages', 'xps', 'oxps', 'abw', 'log', 'msg', 'eml', 'p7s', '1st', 'ans', 'asc', 'diz', 'err', 'faq', 'readme', 'sub', 'ltr', 'dvi', 'xlsx', 'xls', 'ods', 'csv', 'tsv', 'xlsm', 'xlsb', 'xltx', 'xltm', 'xlw', 'xlr', 'wk1', 'wk2', 'wk3', 'wk4', 'wq1', 'sd1', 'dbf', 'dif', 'slk', 'prn', 'numbers', 'fods', 'ots', 'gnumeric', 'pptx', 'ppt', 'odp', 'pptm', 'pps', 'ppsx', 'ppsm', 'pot', 'potx', 'potm', 'sda', 'sdd', 'sti', 'thmx', 'key', 'pez', 'otp']},
39
+ **{ext: 'calibre' for ext in['epub', 'mobi', 'azw3', 'fb2', 'lit', 'prc', 'tcr', 'cbr', 'cbz', 'cb7', 'cbt', 'cba', 'chm', 'djv', 'ibooks', 'kfx', 'pdb', 'snb', 'tmd', 'lrf']},
40
+ **{ext: 'pandoc' for ext in ['html', 'md', 'rst', 'tex']},
41
+ **{ext: 'assimp' for ext in['stl', 'obj', 'fbx', 'dae', '3ds', 'blend', 'ply', 'gltf', 'glb', 'step', 'stp', 'iges', 'igs', 'dxf', 'dwg', 'x3d', 'wrl', 'amf']},
42
+ **{ext: 'fontforge' for ext in['ttf', 'otf', 'woff', 'woff2', 'eot', 'dfont', 'fon', 'pfa', 'pfb', 'suit', 'fnt']},
43
+ **{ext: 'archive_engine' for ext in['zip', 'tar', 'gz', 'bz2', '7z', 'rar', 'xz', 'lzma', 'wim', 'swm', 'cab', 'iso', 'dmg', 'img', 'vdi', 'vhd', 'vmdk', 'squashfs', 'qcow2', 'z', 'taz', 'tgz', 'tbz2', 'tz2', 'tbz', 'jar', 'apk', 'xpi', 'crx', 'nupkg', 'pak', 'lz', 'sit', 'sitx', 'pea', 'ar', 'cpio', 'shar', 'lha', 'lzh', 'alz']},
44
+ **{ext: 'rename' for ext in['py', 'js', 'java', 'c', 'cpp', 'cs', 'rb', 'go', 'rs', 'php', 'swift', 'kt', 'sh', 'bat', 'ps1', 'pl', 'lua', 'm', 'r', 'vbs', 'asm', 'dart', 'ts', 'css', 'scss', 'less', 'txt', 'xml', 'json', 'yaml', 'yml', 'rss', 'atom', 'rdf', 'owl', 'xsd', 'dtd', 'xslt', 'xsl', 'plist', 'ini', 'cfg', 'conf', 'toml', 'sql', 'hdf5', 'h5', 'nc', 'sqlite', 'db', 'mdb', 'accdb', 'bson', 'avro', 'parquet']}
45
+ }
46
+
47
+ def execute_conversion(input_path: str, output_path: str, target_ext: str):
48
+ engine = CONVERSION_ENGINES.get(target_ext)
49
+ if not engine:
50
+ raise ValueError(f"Target format '{target_ext}' is not mapped to an engine.")
51
+
52
+ try:
53
+ if engine == 'rename':
54
+ shutil.copy(input_path, output_path)
55
+ return
56
+ elif engine == 'libreoffice':
57
+ out_dir = os.path.dirname(output_path)
58
+ cmd =['soffice', '--headless', '--convert-to', target_ext, '--outdir', out_dir, input_path]
59
+ elif engine == 'imagemagick':
60
+ cmd =['magick', input_path, output_path]
61
+ elif engine == 'ffmpeg':
62
+ cmd =['ffmpeg', '-y', '-i', input_path, output_path]
63
+ elif engine == 'pandoc':
64
+ cmd =['pandoc', input_path, '-o', output_path]
65
+ elif engine == 'calibre':
66
+ cmd =['ebook-convert', input_path, output_path]
67
+ elif engine == 'assimp':
68
+ cmd = ['assimp', 'export', input_path, output_path]
69
+ elif engine == 'fontforge':
70
+ script = f'Open("{input_path}"); Generate("{output_path}")'
71
+ cmd = ['fontforge', '-lang=ff', '-c', script]
72
+ elif engine == 'archive_engine':
73
+ with tempfile.TemporaryDirectory() as temp_dir:
74
+ subprocess.run(['7z', 'x', f'-o{temp_dir}', input_path], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
75
+ subprocess.run(['7z', 'a', output_path, f'{temp_dir}/*'], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
76
+ return
77
+
78
+ subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
79
+ except FileNotFoundError:
80
+ raise RuntimeError(f"Required server tool for engine '{engine}' is missing or not installed.")
81
+ except subprocess.CalledProcessError as e:
82
+ err_msg = e.stderr.decode('utf-8') if e.stderr else str(e)
83
+ raise RuntimeError(f"Engine Error ({engine}): {err_msg}")
84
+
85
+ @app.post("/convert")
86
+ async def convert_file(
87
+ file: UploadFile = File(...),
88
+ target_format: str = Form(...),
89
+ api_key: str = Depends(get_api_key) # <-- SECURITY BRIDGE CHECK HAPPENS HERE
90
+ ):
91
+ target_format = target_format.lower().strip('.')
92
+
93
+ filename_parts = file.filename.rsplit('.', 1)
94
+ input_ext = filename_parts[-1] if len(filename_parts) > 1 else "tmp"
95
+ base_name = filename_parts[0] if len(filename_parts) > 1 else "file"
96
+
97
+ # Memory-safe chunked streaming (Prevents RAM explosions)
98
+ with NamedTemporaryFile(delete=False, suffix=f".{input_ext}") as tmp_in:
99
+ shutil.copyfileobj(file.file, tmp_in)
100
+ input_path = tmp_in.name
101
+
102
+ output_path = input_path.rsplit('.', 1)[0] + f".{target_format}"
103
+
104
+ try:
105
+ execute_conversion(input_path, output_path, target_format)
106
+
107
+ return FileResponse(
108
+ path=output_path,
109
+ filename=f"{base_name}_converted.{target_format}",
110
+ media_type='application/octet-stream'
111
+ )
112
+ except Exception as e:
113
+ raise HTTPException(status_code=500, detail=str(e))
114
+
115
+ if __name__ == "__main__":
116
+ uvicorn.run(app, host="0.0.0.0", port=7860)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ python-multipart