triflix commited on
Commit
3bb5d2e
·
verified ·
1 Parent(s): 3ce53a9

Upload 13 files

Browse files
.dockerignore ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ .Python
6
+ env/
7
+ venv/
8
+ .env
9
+ .git
10
+ .gitignore
11
+ .pytest_cache/
12
+ .coverage
13
+ htmlcov/
Dockerfile ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Python 3.12 slim image as base
2
+ FROM python:3.12-slim
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Set environment variables
8
+ ENV PYTHONDONTWRITEBYTECODE 1
9
+ ENV PYTHONUNBUFFERED 1
10
+
11
+ # Install system dependencies
12
+ RUN apt-get update \
13
+ && apt-get install -y --no-install-recommends \
14
+ build-essential \
15
+ curl \
16
+ && rm -rf /var/lib/apt/lists/*
17
+
18
+ # Copy requirements first to leverage Docker cache
19
+ COPY requirements.txt .
20
+
21
+ # Install Python dependencies
22
+ RUN pip install --no-cache-dir -r requirements.txt
23
+
24
+ # Copy the rest of the application
25
+ COPY . .
26
+
27
+ # Create necessary directories
28
+ RUN mkdir -p static/css
29
+
30
+ # Expose port 7860
31
+ EXPOSE 7860
32
+
33
+ # Command to run the application
34
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
app/__pycache__/data.cpython-312.pyc ADDED
Binary file (811 Bytes). View file
 
app/__pycache__/main.cpython-312.pyc ADDED
Binary file (2.3 kB). View file
 
app/__pycache__/models.cpython-312.pyc ADDED
Binary file (597 Bytes). View file
 
app/data.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .models import Movie
2
+
3
+ movie_data = [
4
+ Movie(
5
+ title="Rush (2013)",
6
+ embed_url="https://short.icu/N9uHP3Rwd",
7
+ description="The epic rivalry between Formula 1 drivers James Hunt and Niki Lauda.",
8
+ release_year=2013,
9
+ rating=8.1
10
+ ),
11
+ Movie(
12
+ title="Inception (2010)",
13
+ embed_url="https://short.icu/example1",
14
+ description="A thief who steals corporate secrets through dream-sharing technology.",
15
+ release_year=2010,
16
+ rating=8.8
17
+ ),
18
+ Movie(
19
+ title="The Dark Knight (2008)",
20
+ embed_url="https://short.icu/example2",
21
+ description="Batman faces his greatest challenge as the Joker wreaks havoc on Gotham City.",
22
+ release_year=2008,
23
+ rating=9.0
24
+ )
25
+ ]
app/main.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request, Query
2
+ from fastapi.templating import Jinja2Templates
3
+ from fastapi.staticfiles import StaticFiles
4
+ from fastapi.responses import FileResponse
5
+ from pathlib import Path
6
+ from .models import Movie
7
+ from .data import movie_data
8
+
9
+ app = FastAPI(title="Binge")
10
+
11
+ # Create static directory if it doesn't exist
12
+ static_dir = Path("static")
13
+ static_dir.mkdir(exist_ok=True)
14
+ (static_dir / "css").mkdir(exist_ok=True)
15
+
16
+ # Mount static files
17
+ app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
18
+
19
+ # Templates
20
+ templates = Jinja2Templates(directory="templates")
21
+
22
+ @app.get("/")
23
+ async def home(request: Request, search: str | None = Query(None)):
24
+ if search:
25
+ filtered_movies = [
26
+ movie for movie in movie_data
27
+ if search.lower() in movie.title.lower() or
28
+ (movie.description and search.lower() in movie.description.lower())
29
+ ]
30
+ else:
31
+ filtered_movies = movie_data
32
+
33
+ return templates.TemplateResponse(
34
+ "index.html",
35
+ {"request": request, "movies": filtered_movies, "search": search}
36
+ )
37
+
38
+ @app.get("/movie/{movie_id}")
39
+ async def movie_detail(request: Request, movie_id: int):
40
+ if 0 <= movie_id < len(movie_data):
41
+ return templates.TemplateResponse(
42
+ "movie.html",
43
+ {"request": request, "movie": movie_data[movie_id]}
44
+ )
45
+ return {"error": "Movie not found"}
app/models.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+
3
+ class Movie(BaseModel):
4
+ title: str
5
+ embed_url: str
6
+ description: str | None = None
7
+ release_year: int | None = None
8
+ rating: float | None = None
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastapi==0.109.0
2
+ uvicorn==0.27.0
3
+ python-multipart==0.0.6
4
+ jinja2==3.1.3
5
+ pydantic==2.5.3
static/css/main.css ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Add any custom CSS here */
2
+ .aspect-w-16 {
3
+ position: relative;
4
+ padding-bottom: 56.25%; /* 16:9 Aspect Ratio */
5
+ }
6
+
7
+ .aspect-w-16 iframe,
8
+ .aspect-w-16 img {
9
+ position: absolute;
10
+ top: 0;
11
+ left: 0;
12
+ width: 100%;
13
+ height: 100%;
14
+ object-fit: cover;
15
+ }
16
+
17
+ /* Smooth scrolling */
18
+ html {
19
+ scroll-behavior: smooth;
20
+ }
21
+
22
+ /* Custom scrollbar */
23
+ ::-webkit-scrollbar {
24
+ width: 8px;
25
+ }
26
+
27
+ ::-webkit-scrollbar-track {
28
+ background: #121212;
29
+ }
30
+
31
+ ::-webkit-scrollbar-thumb {
32
+ background: #2D2D2D;
33
+ border-radius: 4px;
34
+ }
35
+
36
+ ::-webkit-scrollbar-thumb:hover {
37
+ background: #404040;
38
+ }
39
+
40
+ /* Text truncation */
41
+ .line-clamp-1 {
42
+ overflow: hidden;
43
+ display: -webkit-box;
44
+ -webkit-line-clamp: 1;
45
+ -webkit-box-orient: vertical;
46
+ }
47
+
48
+ /* Mobile optimizations */
49
+ @media (max-width: 768px) {
50
+ .container {
51
+ padding-left: 16px;
52
+ padding-right: 16px;
53
+ }
54
+ }
templates/base.html ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{% block title %}Binge{% endblock %}</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="{{ url_for('static', path='/css/main.css') }}">
9
+ <script>
10
+ tailwind.config = {
11
+ theme: {
12
+ extend: {
13
+ colors: {
14
+ dark: '#121212',
15
+ 'dark-lighter': '#1E1E1E',
16
+ 'dark-accent': '#2D2D2D',
17
+ }
18
+ }
19
+ }
20
+ }
21
+ </script>
22
+ </head>
23
+ <body class="bg-dark text-white min-h-screen flex flex-col">
24
+ <!-- Mobile-first navigation -->
25
+ <nav class="bg-dark-lighter fixed w-full top-0 z-50 border-b border-dark-accent">
26
+ <div class="container mx-auto px-4 py-3">
27
+ <div class="flex items-center justify-between">
28
+ <a href="/" class="text-2xl font-bold tracking-tighter">BINGE</a>
29
+ <button id="menuBtn" class="md:hidden p-2">
30
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
31
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-16 6h16"></path>
32
+ </svg>
33
+ </button>
34
+ <div class="hidden md:flex space-x-6">
35
+ <a href="/" class="hover:text-gray-300 transition-colors">Home</a>
36
+ <a href="#" class="hover:text-gray-300 transition-colors">Movies</a>
37
+ <a href="#" class="hover:text-gray-300 transition-colors">TV Shows</a>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ </nav>
42
+
43
+ <!-- Mobile menu -->
44
+ <div id="mobileMenu" class="hidden fixed inset-0 bg-dark z-40 pt-16">
45
+ <div class="container mx-auto px-4 py-6">
46
+ <div class="flex flex-col space-y-4">
47
+ <a href="/" class="text-lg hover:text-gray-300 transition-colors">Home</a>
48
+ <a href="#" class="text-lg hover:text-gray-300 transition-colors">Movies</a>
49
+ <a href="#" class="text-lg hover:text-gray-300 transition-colors">TV Shows</a>
50
+ </div>
51
+ </div>
52
+ </div>
53
+
54
+ <main class="container mx-auto px-4 py-8 mt-16 flex-grow">
55
+ {% block content %}{% endblock %}
56
+ </main>
57
+
58
+ <footer class="bg-dark-lighter py-6 mt-auto">
59
+ <div class="container mx-auto px-4 text-center text-sm text-gray-400">
60
+ <p>&copy; 2024 Binge. All rights reserved.</p>
61
+ </div>
62
+ </footer>
63
+
64
+ <script>
65
+ // Mobile menu toggle
66
+ const menuBtn = document.getElementById('menuBtn');
67
+ const mobileMenu = document.getElementById('mobileMenu');
68
+
69
+ menuBtn.addEventListener('click', () => {
70
+ mobileMenu.classList.toggle('hidden');
71
+ });
72
+ </script>
73
+ </body>
74
+ </html>
templates/index.html ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Binge - Watch Movies Online{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="space-y-8">
7
+ <!-- Search bar -->
8
+ <div class="max-w-2xl mx-auto mb-12">
9
+ <form action="/" method="get" class="relative">
10
+ <input
11
+ type="search"
12
+ name="search"
13
+ placeholder="Search movies..."
14
+ value="{{ search or '' }}"
15
+ class="w-full bg-dark-lighter border border-dark-accent rounded-lg px-4 py-3 pl-12 focus:outline-none focus:border-gray-500 transition-colors"
16
+ >
17
+ <svg class="w-6 h-6 text-gray-400 absolute left-3 top-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
18
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
19
+ </svg>
20
+ </form>
21
+ </div>
22
+
23
+ {% if not movies %}
24
+ <div class="text-center py-12">
25
+ <p class="text-gray-400">No movies found. Try a different search.</p>
26
+ </div>
27
+ {% else %}
28
+ <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
29
+ {% for movie in movies %}
30
+ <div class="bg-dark-lighter rounded-lg overflow-hidden shadow-lg transform hover:scale-105 transition-transform duration-200">
31
+ <a href="/movie/{{ loop.index0 }}" class="block">
32
+ <div class="aspect-w-16 aspect-h-9 bg-dark-accent">
33
+ <img
34
+ src="https://via.placeholder.com/300x450"
35
+ alt="{{ movie.title }}"
36
+ class="w-full h-full object-cover opacity-90 hover:opacity-100 transition-opacity"
37
+ >
38
+ </div>
39
+ <div class="p-4 space-y-2">
40
+ <h2 class="text-lg font-semibold line-clamp-1">{{ movie.title }}</h2>
41
+ {% if movie.release_year %}
42
+ <p class="text-sm text-gray-400">{{ movie.release_year }}</p>
43
+ {% endif %}
44
+ {% if movie.rating %}
45
+ <div class="flex items-center">
46
+ <svg class="w-5 h-5 text-yellow-500" fill="currentColor" viewBox="0 0 20 20">
47
+ <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
48
+ </svg>
49
+ <span class="ml-1 text-sm">{{ movie.rating }}/10</span>
50
+ </div>
51
+ {% endif %}
52
+ </div>
53
+ </a>
54
+ </div>
55
+ {% endfor %}
56
+ </div>
57
+ {% endif %}
58
+ </div>
59
+ {% endblock %}
templates/movie.html ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}{{ movie.title }} - Binge{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="max-w-4xl mx-auto space-y-8">
7
+ <div class="flex flex-col md:flex-row md:items-start md:space-x-8">
8
+ <div class="w-full md:w-2/3">
9
+ <div class="aspect-w-16 aspect-h-9 bg-dark-lighter rounded-lg overflow-hidden shadow-lg">
10
+ <iframe
11
+ src="{{ movie.embed_url }}"
12
+ frameborder="0"
13
+ scrolling="0"
14
+ allowfullscreen
15
+ class="w-full h-full"
16
+ ></iframe>
17
+ </div>
18
+ </div>
19
+
20
+ <div class="w-full md:w-1/3 mt-6 md:mt-0">
21
+ <h1 class="text-2xl font-bold mb-4">{{ movie.title }}</h1>
22
+
23
+ <div class="flex items-center space-x-4 mb-6">
24
+ {% if movie.release_year %}
25
+ <div class="bg-dark-lighter px-3 py-1 rounded text-sm">
26
+ {{ movie.release_year }}
27
+ </div>
28
+ {% endif %}
29
+
30
+ {% if movie.rating %}
31
+ <div class="bg-dark-lighter px-3 py-1 rounded flex items-center">
32
+ <svg class="w-4 h-4 text-yellow-500" fill="currentColor" viewBox="0 0 20 20">
33
+ <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
34
+ </svg>
35
+ <span class="ml-1">{{ movie.rating }}/10</span>
36
+ </div>
37
+ {% endif %}
38
+ </div>
39
+
40
+ {% if movie.description %}
41
+ <div class="bg-dark-lighter p-4 rounded-lg">
42
+ <h2 class="text-lg font-semibold mb-2">About</h2>
43
+ <p class="text-gray-300 text-sm">{{ movie.description }}</p>
44
+ </div>
45
+ {% endif %}
46
+ </div>
47
+ </div>
48
+ </div>
49
+ {% endblock %}