Repository: IM-THE-GRASS/movie-streaming-site Branch: main Commit: 6e90d0f21bac Files: 14 Total size: 25.6 KB Directory structure: gitextract_z5w8x00p/ ├── .gitattributes ├── .gitignore ├── README.md ├── movie_streaming_site/ │ ├── __init__.py │ ├── components/ │ │ ├── keybind.py │ │ ├── loading.py │ │ ├── moviecard.py │ │ └── search.py │ ├── movie_streaming_site.py │ ├── pages/ │ │ ├── player.py │ │ └── search.py │ └── state.py ├── requirements.txt └── rxconfig.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto ================================================ FILE: .gitignore ================================================ *.db *.py[cod] .env .web __pycache__/ assets/external/ ================================================ FILE: README.md ================================================ # Movie Streaming Site A site built in [Reflex](https://reflex.dev) that uses the [MoviesAPI.club](https://moviesapi.club) and the [TMDB](https://www.themoviedb.org/) api for movies streaming of thousands of movies for 100% free. Search, streaming, recomended and getting info about movies are all features of the site that can be had for free and with barely any loading times. ### Self hosting: To self host and for developmentyou need python 3.11 or above[^1] in terminal, navigate to the `movie_streaming_site` folder and run `python -m pip install -r /requirements.txt` Once done, create a file called `.env` and using notepad write ` auth="{YOUR TMDBAPI KEY HERE}" ` and replace `{YOUR TMDBAPI KEY HERE}` with an api key from [TMDB api](https://developer.themoviedb.org/v4/reference/intro/getting-started) to run just write `python -m reflex run` in the command line [^1]:version 3.11 or above is fine, older versions may work but I haven't tested them ### Demo / screenshots: https://github.com/user-attachments/assets/35242525-660c-4737-be4e-bd7110d54232 ![image](https://github.com/user-attachments/assets/05263ca7-04e4-45f3-bd30-5e163a2a1bfb) ![image](https://github.com/user-attachments/assets/ce6551bf-5ba1-43e3-a64d-2e56085c8bf1) ##### The site was built for [Hack Club Arcade](https://hackclub.com/arcade/) ================================================ FILE: movie_streaming_site/__init__.py ================================================ ================================================ FILE: movie_streaming_site/components/keybind.py ================================================ import reflex as rx class GlobalKeyWatcher(rx.Fragment): # List of keys to trigger on keys: rx.Var[list[str]] = [] # The event handler that will be called bind: rx.EventHandler[lambda ev: [ev.key]] def _get_imports(self) -> rx.utils.imports.ImportDict: return rx.utils.imports.merge_imports( super()._get_imports(), { "react": { rx.utils.imports.ImportVar( tag="useEffect" ) } }, ) def _get_hooks(self) -> str | None: return """ useEffect(() => { const handle_key = (_ev) => { if (%s.includes(_ev.key)) %s } document.addEventListener("keydown", handle_key, false); return () => { document.removeEventListener("keydown", handle_key, false); } }) """ % ( self.keys, rx.utils.format.format_event_chain( self.event_triggers["bind"] ), ) def render(self): return "" # No visual element, hooks only Keybind = GlobalKeyWatcher.create ================================================ FILE: movie_streaming_site/components/loading.py ================================================ import reflex as rx from reflex_lottiefiles import LottieFiles def loading(): rx.center( rx.vstack( rx.center( rx.heading("Loading, please wait"), width="90vw", ), LottieFiles( src="https://lottie.host/5ff06a80-3f45-4dd3-8737-f4cf62ba3d48/X5hdVEjbNK.lottie", autoplay=True, loop=True, width="90vw", height=["40vh", "40vh", "40vh"], ) ), width="90vw", height="80vh" ), ================================================ FILE: movie_streaming_site/components/moviecard.py ================================================ import reflex as rx from reflex_motion import motion def movie_card(title: str, year: str, duration: str, link:str, img:str = "https://via.placeholder.com/316x421", description:str = ""): return rx.hover_card.root( rx.hover_card.trigger( rx.link( motion( rx.image( src=img, width="100%", height="47vh", object_fit="contain" ), rx.vstack( rx.hstack( rx.text(year, color="#C0C0C0", font_size="2vh", font_style="italic"), rx.text(duration, color="#C0C0C0", font_size="2vh", font_style="italic", text_align="right", width="100%"), width="100%" ), rx.heading(title, color="#F2F2F2", font_size="4vh", font_weight="800"), spacing="0.5vh", align_items="flex-start", padding="1.7vh", ), padding="1vw", width="100%", #height="60.4vh", bg="#1A1A1A", border_radius="1vh", #border="0.1vh solid #D9D9D9", while_hover={"scale": 1.02}, while_tap={"scale": 0.98}, transition={"type": "spring", "stiffness": 400, "damping": 17}, ), padding="1vw", width="100%", border_radius="1vh", href=link, is_external=True ) ), rx.hover_card.content( rx.hstack( rx.vstack( rx.heading( title, width="30vh" ), rx.image( src=img, width="30vh" ), width="90vh" ), rx.text( description ) ), side="right" ), open_delay=900 ) ================================================ FILE: movie_streaming_site/components/search.py ================================================ import reflex as rx from movie_streaming_site.state import State from reflex_motion import motion from movie_streaming_site.components.keybind import Keybind def search(): return rx.hstack( Keybind( keys=["Enter"], bind=State.go_search ), motion( rx.button( rx.icon(tag="search", color="white", size=30), variant="ghost", on_click=lambda: State.go_search("A") ), while_hover={"scale": 1.05}, transition={"type": "spring", "stiffness": 400, "damping": 17}, ), motion( rx.input( placeholder="Search", color="white", font_size="3.5vh", width="100%", background_color="rgba(255, 255, 255, 0)", border="none", border_width="0px", height="30", value=State.search_value, on_change=State.change_search_value ), width="100%", while_hover={"scale": 1.02}, transition={"type": "spring", "stiffness": 400, "damping": 17}, ), on_focus= State.search_focus, on_blur=State.search_blur, bg="rgba(0, 0, 0, 0.29)", border_radius="1087vh", padding="0.5vh", width=["90vw", "90vw", "48vw"], position="absolute", left=["5vw","5vw", "28vw"], top="2vh", ), ================================================ FILE: movie_streaming_site/movie_streaming_site.py ================================================ import reflex as rx from movie_streaming_site.components.moviecard import movie_card from movie_streaming_site.state import State from movie_streaming_site.components.search import search from reflex_lottiefiles import LottieFiles from movie_streaming_site.components.loading import loading from movie_streaming_site.pages.player import movieplayer from movie_streaming_site.pages.search import search_page from reflex_motion import motion class slider(rx.Component): library = "nuka-carousel" tag = "Carousel" autoplay:rx.Var[bool] showDots:rx.Var[bool] wrapMode:rx.Var[str] autoplayInterval:rx.Var[int] scrollDistance:rx.Var[str] @rx.page(on_load=State.on_load) def index(): return rx.box( rx.box( Slider( rx.image(src="https://media.themoviedb.org/t/p/w1920_and_h800_multi_faces/qGQf2OHIkoh89K8XeKQzhxczf96.jpg", width="110vw", height="108vh", object_fit = "cover"), rx.image(src="https://media.themoviedb.org/t/p/w1920_and_h800_multi_faces/stKGOm8UyhuLPR9sZLjs5AkmncA.jpg", width="110vw", height="108vh", object_fit = "cover"), rx.image(src="https://media.themoviedb.org/t/p/w1920_and_h800_multi_faces/eHz61dRrYZB16glXDttV0CnJf6j.jpg", width="110vw", height="108vh", object_fit = "cover"), rx.image(src="https://media.themoviedb.org/t/p/w1920_and_h800_multi_faces/mabuNsGJgRuCTuGqjFkWe1xdu19.jpg", width="110vw", height="108vh", object_fit = "cover"), autoplay=True, showDots=False, autoplayInterval=6000, scrollDistance="slide", wrapMode="wrap", overflow="hidden" ), rx.box( width="100vw", height="108vh", position="absolute", left="0", top="0", bg="linear-gradient(180deg,rgba(18, 18, 18, 0.25) 0%, rgba(18, 18, 18, 0.77) 50%, rgba(18, 18, 18, 0.90) 77%, #121212 93%)", overflow="hidden" ), motion( position="absolute", left="7vw", top="70vh", width="24vw", height="16vh", while_hover={"scale": 1.2}, while_tap={"scale": 0.9}, transition={"type": "spring", "stiffness": 400, "damping": 17}, ), rx.heading( "OPENSTREAM", font_size="15vw", font_weight="bolder", text_wrap = "nowrap", letter_spacing = "-4px", color="white", position="absolute", left="2.5vw", top="20vh", width="85vw", height="30vh", line_height="10vw", display = ["block", "none", "none"] ), rx.heading( "Thousands of", font_size=["10vw", "12vw", "14vh"], font_weight="1000", text_wrap = "nowrap", color="white", position="absolute", left="7vw", top=["30vh","20vh", "20vh"], width=["85vw", "50vw","50vw"], height="30vh", line_height=["10vw", "10vw", "14vh"], ), rx.heading( "movies, right at", font_size=["10vw", "12vw", "14vh"], font_weight="1000", text_wrap = "nowrap", color="white", position="absolute", left="7vw", top=["39vh", "30vh", "34vh"], width=["85vw", "50vw","50vw"], height="30vh", line_height=["10vw", "10vw", "14vh"], ), rx.heading( "your fingertips", font_size=["10vw", "12vw", "14vh"], font_weight="1000", text_wrap = "nowrap", color="white", position="absolute", left="7vw", top=["48vh", "40vh", "48vh"], width=["85vw", "50vw","50vw"], height="30vh", line_height=["10vw", "10vw", "14vh"], ), search(), width="100vw", height="111vh", position="relative", overflow="hidden" ), rx.center( rx.cond( State.loading, rx.center( rx.vstack( rx.center( rx.heading("Loading, please wait"), width="90vw", ), LottieFiles( src="https://lottie.host/5ff06a80-3f45-4dd3-8737-f4cf62ba3d48/X5hdVEjbNK.lottie", autoplay=True, loop=True, width="90vw", height=["40vh", "40vh", "40vh"], ) ), width="90vw", height="80vh" ), ), rx.desktop_only( rx.grid( rx.foreach( State.now_playing, lambda info, index: movie_card(info["title"], info["year"], f"{info['runtime']} mins", info["link"], info["poster"], description=info["description"]) ), spacing="1.5vh", columns="4", position="absolute", left="1.5vw", top="105vh", width="100%", overflow="hidden" ), ), rx.mobile_and_tablet( rx.grid( rx.foreach( State.now_playing, lambda info, index: movie_card(info["title"], info["year"], f"{info['runtime']} mins", info["link"], info["poster"], description=info["description"]) ), spacing="1.5vh", columns="1", position="absolute", left="1.5vw", top="105vh", width="100%", overflow="hidden" ), ), width="100%", overflow="hidden" ), bg="#121212", position="relative", overflow_x="hidden", ) Slider = slider.create def testpage(): return rx.box( Slider( rx.image(src="https://commerce.nearform.com/open-source/nuka-carousel/img/pexels-01.jpg"), rx.image(src="https://commerce.nearform.com/open-source/nuka-carousel/img/pexels-02.jpg"), rx.image(src="https://commerce.nearform.com/open-source/nuka-carousel/img/pexels-03.jpg"), rx.image(src="https://commerce.nearform.com/open-source/nuka-carousel/img/pexels-04.jpg"), autoplay=True, showDots=True, autoplayInterval=3000, wrapMode="wrap", ) ) style = { "body":{ "background-color":"#121212", "overflow":"hidden" }, "html":{ "overflow":"hidden" } } app = rx.App(style = style) app.add_page(search_page) app.add_page(movieplayer) app.add_page(index) app.add_page(testpage) ================================================ FILE: movie_streaming_site/pages/player.py ================================================ import reflex as rx from reflex_lottiefiles import LottieFiles from movie_streaming_site.state import State from movie_streaming_site.components.search import search @rx.page(route="/movieplayer/[movieid]", on_load=State.on_load) def movieplayer(): return rx.box( search(), rx.cond( State.loading, rx.center( rx.vstack( rx.center( rx.heading("Loading, please wait"), width="90vw", ), LottieFiles( src="https://lottie.host/5ff06a80-3f45-4dd3-8737-f4cf62ba3d48/X5hdVEjbNK.lottie", autoplay=True, loop=True, width="90vw", height=["40vh", "40vh", "40vh"], ) ), width="90vw", height="80vh" ), rx.box( rx.text(State.current_movie["title"], font_size="10.5vh", font_weight="800"), rx.hstack( rx.desktop_only( rx.html( State.movie_iframe, width="50vw" ), ), rx.mobile_and_tablet( rx.html( State.movie_iframe, width="85vw" ), ), #rx.box(width="960px", height="540px", bg="#D9D9D9"), rx.desktop_only( rx.vstack( rx.text( "Description", color="white", font_size="4vh", font_weight="800" ), rx.text( State.current_movie["description"], color="white", font_size="3vh", max_width="28vw" ), align_items="flex-start", ), ), rx.desktop_only( rx.vstack( movie_info_item(State.current_movie["date"], "calendar"), movie_info_item(State.current_movie["revenue"], "dollar-sign"), movie_info_item(State.current_movie["runtime"], "clock"), rx.cond( State.current_movie["site"], rx.link( movie_info_item("Site", "globe"), href=State.current_movie["site"], is_external=True ), ), rx.cond( State.current_movie["tmdb_link"], rx.link( movie_info_item("TMDB", "clock"), href=State.current_movie["tmdb_link"], is_external=True ), ), rx.cond( State.current_movie["imdb_link"], rx.link( movie_info_item("IMDB", "clock"), href=State.current_movie["imdb_link"], is_external=True ) ) ), ), spacing="2vw", ), ) ), width = "100%", padding="5.5vh", ) def movie_info_item(text, icon): return rx.hstack( rx.center( rx.icon( icon, ), ), rx.center( rx.text(text, color="white", font_size="2.5vh"), ), spacing="1vw", ) ================================================ FILE: movie_streaming_site/pages/search.py ================================================ import reflex as rx from movie_streaming_site.state import State from movie_streaming_site.components.search import search from movie_streaming_site.components.moviecard import movie_card @rx.page(route="/search/[query]", on_load=State.search_on_load) def search_page(): return rx.box( search(), rx.text( State.search_query, font_size="10.5vh", font_weight="800", padding_top="5vh" ), rx.desktop_only( rx.grid( rx.foreach( State.search_results, lambda info, index: movie_card(info["title"], info["year"], "", info["link"], info["poster"], description=info["description"]) ), spacing="1.5vh", columns="4", width="100%" ), ), rx.mobile_and_tablet( rx.grid( rx.foreach( State.search_results, lambda info, index: movie_card(info["title"], info["year"], "", info["link"], info["poster"], description=info["description"]) ), spacing="1.5vh", columns="1", width="100%" ), ) ) ================================================ FILE: movie_streaming_site/state.py ================================================ import reflex as rx import requests import dotenv import os import json dotenv.load_dotenv() auth = os.environ.get("auth") class State(rx.State): now_playing:list[dict[str, str]] movie_iframe:str current_movie:dict[str, str] loading:bool = True search_results:list[dict[str, str]] search_focused:bool = False search_value:str def change_search_value(self, new): self.search_value = new @rx.var def search_url(self) -> str: return f"/search/{self.search_value}/" def search_focus(self): self.search_focused = True def search_blur(self): self.search_focused = False def go_search(self, _): if self.search_value and self.search_focused: return rx.redirect(f"/search/{self.search_value}/") @rx.var def search_query(self) -> str: return self.router.page.params.get("query", "") @rx.var def movie_id(self) -> str: return self.router.page.params.get("movieid", "") def search(self, query): url = f"https://api.themoviedb.org/3/search/movie?query={query}" headers = { "accept": "application/json", "Authorization": f"Bearer {os.environ.get('auth')}" } response = requests.get(url, headers=headers) data = json.loads(response.text) print(data) results = data["results"] for result in results: result.pop("adult") result.pop("genre_ids") result["id"] = str(result["id"]) result.pop("original_language") result.pop("original_title") result.pop("popularity") result["year"] = result["release_date"][:4] result.pop("release_date") result.pop("video") result.pop("vote_average") result.pop("vote_count") result["link"] = f"/movieplayer/{result['id']}" result["description"] = result["overview"] result["poster"] = f"https://image.tmdb.org/t/p/w300_and_h450_bestv2/{result['poster_path']}" print(results) return results def search_on_load(self): print(f"Searched for {self.search_query}") self.search_results = self.search(self.search_query) def get_current_movie(self): movie_id = self.movie_id data = self.get_movie_data(movie_id) print(data) try: result = { "imdb_link":f"https://www.imdb.com/title/{data['imdb_id']}/", "tmdb_link":f"https://www.themoviedb.org/movie/{data['id']}", "description":data["overview"], "runtime":f"{data['runtime']} min", "revenue":f"${data['revenue']}", "title":data["title"], "date":data["release_date"] } except: return try: result["site"]= data["homepage"] except: pass return result def get_movie_data(self, id): url = f"https://api.themoviedb.org/3/movie/{id}?language=en-US" response = requests.get( url, headers={ "accept": "application/json", "Authorization": f"Bearer {auth}" } ) data = json.loads(response.text) return data def get_now_playing_movies(self): url = "https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=1" response = requests.get( url, headers={ "accept": "application/json", "Authorization": f"Bearer {auth}" } ) data = json.loads(response.text) results = data["results"] result_list = [] for result in results: result_list.append( { "poster":f"https://image.tmdb.org/t/p/w300_and_h450_bestv2/{result['poster_path']}", "title": result["title"], "year":result["release_date"][:4], "description":result["overview"], "runtime":self.get_movie_data(result["id"])["runtime"], "link":f"/movieplayer/{result['id']}" } ) return result_list def on_load(self): self.loading = True print("start load") self.movie_iframe = f' ' print("load iframe") self.now_playing = self.get_now_playing_movies() print("load now playing") self.current_movie = self.get_current_movie() print("load current") self.loading=False return rx.toast("This website is for educational purposes only, I am not distrubuiting these movies, this is just a wrapper for https://moviesapi.club", duration=5000) ================================================ FILE: requirements.txt ================================================ python-dotenv==1.0.1 reflex-lottiefiles==0.0.2 reflex-motion==0.0.1 reflex==0.5.9 ================================================ FILE: rxconfig.py ================================================ import reflex as rx config = rx.Config( app_name="movie_streaming_site", frontend_port=60769, backend_port=40167, api_url="https://openstreamapi.thegrass.xyz" )