Fraser commited on
Commit
fec820d
·
0 Parent(s):

Initial backend setup with Gradio

Browse files
Files changed (3) hide show
  1. README.md +95 -0
  2. app.py +139 -0
  3. requirements.txt +2 -0
README.md ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Odyssey Backend
3
+ emoji: 🎥
4
+ colorFrom: purple
5
+ colorTo: blue
6
+ sdk: gradio
7
+ sdk_version: 5.38.2
8
+ app_file: app.py
9
+ pinned: false
10
+ short_description: Video upload backend for Odyssey adventure game
11
+ ---
12
+
13
+ # 🎥 Odyssey Backend
14
+
15
+ Backend service for the [Odyssey](https://huggingface.co/spaces/Fraser/odyssey) interactive video adventure game.
16
+
17
+ ## Features
18
+
19
+ - **Video Upload**: Upload generated adventure videos to HuggingFace dataset
20
+ - **UUID Generation**: Each video gets a unique identifier
21
+ - **Public URLs**: Serve videos via HuggingFace CDN
22
+ - **Gradio API**: Simple API for programmatic access
23
+
24
+ ## Architecture
25
+
26
+ - **Frontend**: [odyssey](https://huggingface.co/spaces/Fraser/odyssey) - Interactive game interface
27
+ - **Backend**: This Space - Video upload service
28
+ - **Storage**: [odyssey-videos](https://huggingface.co/datasets/Fraser/odyssey-videos) - Dataset of generated videos
29
+
30
+ ## API Usage
31
+
32
+ ### JavaScript/TypeScript
33
+
34
+ ```javascript
35
+ import { Client } from "@gradio/client";
36
+
37
+ const client = await Client.connect("Fraser/odyssey-backend");
38
+
39
+ // Upload video
40
+ const result = await client.predict("/upload_video", {
41
+ video_file: blob // or file handle
42
+ });
43
+
44
+ // Parse result
45
+ const data = JSON.parse(result.data[0]);
46
+ console.log(data.uuid, data.url, data.share);
47
+ ```
48
+
49
+ ### Python
50
+
51
+ ```python
52
+ from gradio_client import Client
53
+
54
+ client = Client("Fraser/odyssey-backend")
55
+
56
+ # Upload video
57
+ result = client.predict("/upload_video", video_file="path/to/video.mp4")
58
+ ```
59
+
60
+ ## Deployment
61
+
62
+ ### Environment Variables
63
+
64
+ Set these in Space Settings → Repository secrets:
65
+
66
+ - `HF_TOKEN`: HuggingFace token with write permissions to `Fraser/odyssey-videos` dataset
67
+
68
+ ### Local Development
69
+
70
+ ```bash
71
+ pip install -r requirements.txt
72
+ export HF_TOKEN=your_token_here
73
+ python app.py
74
+ # Server runs at http://localhost:7860
75
+ ```
76
+
77
+ ## Dataset Structure
78
+
79
+ All videos are stored in `Fraser/odyssey-videos`:
80
+
81
+ ```
82
+ {uuid}.mp4
83
+ {uuid}.mp4
84
+ ...
85
+ ```
86
+
87
+ Each video can be accessed via:
88
+ - Direct URL: `https://huggingface.co/datasets/Fraser/odyssey-videos/resolve/main/{uuid}.mp4`
89
+ - Frontend URL: `https://fraser-odyssey.static.hf.space/?id={uuid}`
90
+
91
+ ## Tech Stack
92
+
93
+ - **Gradio**: API framework and web interface
94
+ - **HuggingFace Hub**: Dataset storage backend
95
+ - **Python**: Core server logic
app.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py - Odyssey Backend (Gradio)
2
+ import os
3
+ import uuid
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Tuple
7
+ import gradio as gr
8
+ from huggingface_hub import HfApi
9
+
10
+ # ===== Config =====
11
+ REPO_ID = "Fraser/odyssey-videos" # dataset to write to
12
+ TOKEN = os.environ.get("HF_TOKEN") # set in Space Secrets
13
+ if not TOKEN:
14
+ raise RuntimeError("Missing HF_TOKEN secret")
15
+
16
+ STAGING = Path("/tmp/staging")
17
+ STAGING.mkdir(parents=True, exist_ok=True)
18
+
19
+ api = HfApi(token=TOKEN)
20
+
21
+ def _public_url(uuid_str: str) -> str:
22
+ """Build the public resolve URL for dataset files"""
23
+ return f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/{uuid_str}.mp4"
24
+
25
+ def upload_video(video_file) -> Tuple[str, str]:
26
+ """
27
+ Upload a video file to the Fraser/odyssey-videos dataset
28
+
29
+ Args:
30
+ video_file: Video file (mp4)
31
+
32
+ Returns:
33
+ JSON string with: {"uuid": "...", "url": "...", "share": "..."}
34
+ """
35
+ if video_file is None:
36
+ return json.dumps({"error": "No video file provided"}), ""
37
+
38
+ try:
39
+ # Generate UUID
40
+ uid = str(uuid.uuid4())
41
+
42
+ # Get the uploaded file path
43
+ video_path = Path(video_file)
44
+
45
+ # Upload to HuggingFace dataset
46
+ api.upload_file(
47
+ path_or_fileobj=str(video_path),
48
+ path_in_repo=f"{uid}.mp4",
49
+ repo_id=REPO_ID,
50
+ repo_type="dataset",
51
+ commit_message=f"Add video {uid}.mp4",
52
+ )
53
+
54
+ # Build response
55
+ url = _public_url(uid)
56
+ result = {
57
+ "uuid": uid,
58
+ "url": url,
59
+ "share": f"https://fraser-odyssey.static.hf.space/?id={uid}"
60
+ }
61
+
62
+ return json.dumps(result), url
63
+
64
+ except Exception as e:
65
+ return json.dumps({"error": str(e)}), ""
66
+
67
+ def get_video_url(uuid_str: str) -> str:
68
+ """Get the public URL for a video by UUID"""
69
+ if not uuid_str:
70
+ return ""
71
+ return _public_url(uuid_str)
72
+
73
+ # ===== Gradio Interface =====
74
+
75
+ with gr.Blocks(title="Odyssey Video Backend") as demo:
76
+ gr.Markdown("""
77
+ # 🎬 Odyssey Video Backend
78
+
79
+ Backend service for uploading Odyssey adventure videos to HuggingFace datasets.
80
+
81
+ This Space is primarily accessed programmatically by the [Odyssey frontend](https://huggingface.co/spaces/Fraser/odyssey-frontend).
82
+ """)
83
+
84
+ with gr.Tab("Upload Video"):
85
+ gr.Markdown("### Upload a video to the odyssey-videos dataset")
86
+
87
+ video_input = gr.Video(label="Video File (MP4)")
88
+ upload_btn = gr.Button("Upload to Dataset", variant="primary")
89
+
90
+ result_json = gr.Textbox(label="Result (JSON)", lines=5)
91
+ result_url = gr.Textbox(label="Video URL", interactive=False)
92
+
93
+ upload_btn.click(
94
+ fn=upload_video,
95
+ inputs=[video_input],
96
+ outputs=[result_json, result_url]
97
+ )
98
+
99
+ with gr.Tab("Get Video URL"):
100
+ gr.Markdown("### Get public URL for a video by UUID")
101
+
102
+ uuid_input = gr.Textbox(label="Video UUID", placeholder="Enter UUID...")
103
+ get_url_btn = gr.Button("Get URL")
104
+ url_output = gr.Textbox(label="Video URL", interactive=False)
105
+
106
+ get_url_btn.click(
107
+ fn=get_video_url,
108
+ inputs=[uuid_input],
109
+ outputs=[url_output]
110
+ )
111
+
112
+ gr.Markdown("""
113
+ ---
114
+
115
+ ## API Usage
116
+
117
+ ### JavaScript/TypeScript
118
+ ```javascript
119
+ import { Client } from "@gradio/client";
120
+
121
+ const client = await Client.connect("Fraser/odyssey-backend");
122
+
123
+ // Upload video
124
+ const result = await client.predict("/upload_video", {
125
+ video_file: blob // or file handle
126
+ });
127
+ ```
128
+
129
+ ### Python
130
+ ```python
131
+ from gradio_client import Client
132
+
133
+ client = Client("Fraser/odyssey-backend")
134
+ result = client.predict("/upload_video", video_file="path/to/video.mp4")
135
+ ```
136
+ """)
137
+
138
+ if __name__ == "__main__":
139
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio
2
+ huggingface_hub