diff --git a/.env b/.env index c068232..bec51e5 100644 --- a/.env +++ b/.env @@ -21,3 +21,5 @@ SUBPROCESS_VERBOSE_OUTPUT=False #Number of gunicorn worker (should be set accordingly to your hardware) NUM_GUNICORN_WORKER=4 + +TMBD_KEY= diff --git a/backend/StreamServerApp/database_utils.py b/backend/StreamServerApp/database_utils.py index b999ff0..a6b5d56 100644 --- a/backend/StreamServerApp/database_utils.py +++ b/backend/StreamServerApp/database_utils.py @@ -22,14 +22,16 @@ from StreamServerApp.media_management.fileinfo import createfileinfo, readfileinfo from StreamServerApp.media_processing import prepare_video, get_video_type_and_info -from StreamServerApp.tasks import get_subtitles_async +from StreamServerApp.tasks import get_subtitles_async, download_cover_async from StreamingServer import settings import logging +from enum import Enum logger = logging.getLogger("root") + def delete_DB_Infos(): """ delete all videos, movies and series in the db """ @@ -144,6 +146,10 @@ def update_db_from_local_folder(base_path, remote_url, keep_files=False, async_u cache.set("processing_state", "finished", timeout=None) cache.delete("processing_file") +class Add_video_return_value(Enum): + NO_SERIES_MOVIES_CREATED = 0 + MOVIE_CREATED = 1 + SERIES_CREATED = 2 @shared_task def add_one_video_to_database(full_path, @@ -211,7 +217,7 @@ def add_one_video_to_database(full_path, v.episode = video_type_and_info['episode'] if created: - return_value = 2 + return_value = Add_video_return_value.SERIES_CREATED.value elif video_type_and_info['type'] == 'Movie': movie, created = Movie.objects.get_or_create( @@ -219,9 +225,14 @@ def add_one_video_to_database(full_path, v.movie = movie if created: - return_value = 1 + return_value = Add_video_return_value.MOVIE_CREATED.value v.save() + if os.getenv('TMBD_KEY'): + if return_value in [Add_video_return_value.SERIES_CREATED.value, Add_video_return_value.MOVIE_CREATED.value]: + download_cover_async.delay( + v.id, video_type_and_info['title'], True if video_type_and_info['type'] == 'Series' else False) + for ov_subtitle_path in video_infos["ov_subtitles"]: ov_sub = Subtitle() webvtt_subtitles_relative_path = os.path.relpath( diff --git a/backend/StreamServerApp/media_management/cover_downloader.py b/backend/StreamServerApp/media_management/cover_downloader.py new file mode 100644 index 0000000..e0e976f --- /dev/null +++ b/backend/StreamServerApp/media_management/cover_downloader.py @@ -0,0 +1,63 @@ +import requests +import os + +import logging +logger = logging.getLogger("root") + + +class cover_downloader: + + def __init__(self): + self.auth_key = "" + env_auth_key = os.getenv('TMBD_KEY') + if not env_auth_key: + logger.error("cover downloader: no auth key") + return + self.auth_key = env_auth_key + + configuration_url = 'https://api.themoviedb.org/3/configuration?&api_key={}'.format( + self.auth_key ) + + response = requests.get(configuration_url) + if response.status_code != 200: + logger.error("cover downloader: Failed to get configuration") + return + value = response.json() + + self.base_url = value["images"]["base_url"] + self.poster_size = value["images"]["poster_sizes"][3] + + def download_cover(self, name, outputfile, is_tv_show=False): + + if self.auth_key: + + if is_tv_show: + api_url = 'https://api.themoviedb.org/3/search/tv?query={}&api_key={}'.format( + name.replace(" ", "+"), self.auth_key) + else: + api_url = 'https://api.themoviedb.org/3/search/movie?query={}&api_key={}'.format( + name.replace(" ", "+"), self.auth_key) + response = requests.get(api_url) + if response.status_code != 200: + logger.error("cover downloader: Failed to search movie") + return -1 + value = response.json() + + if len(value["results"]) > 0: + + poster_url = "{}/{}{}".format(self.base_url, self.poster_size, + value["results"][0]["poster_path"]) + response = requests.get(poster_url) + if response.status_code != 200: + logger.error("cover downloader: Failed to download cover") + return -1 + + with open(outputfile, mode="wb") as file: + file.write(response.content) + + return 1 + else: + return -1 + else: + logger.error("cover downloader: No properly initialized") + return -1 diff --git a/backend/StreamServerApp/migrations/0026_auto_20240618_0917.py b/backend/StreamServerApp/migrations/0026_auto_20240618_0917.py new file mode 100644 index 0000000..debc79b --- /dev/null +++ b/backend/StreamServerApp/migrations/0026_auto_20240618_0917.py @@ -0,0 +1,46 @@ +# Generated by Django 4.2 on 2024-06-18 09:17 + +from django.db import migrations +from StreamServerApp.models import Video, Series, Movie, Subtitle +from StreamServerApp.media_management.cover_downloader import cover_downloader +import os + +cvdwnld = cover_downloader() + +def get_cover(apps, schema_editor): + + if os.getenv('TMBD_KEY'): + + movies = Movie.objects.all() + for movie in movies: + output_file = "/usr/static/{}.jpeg".format(movie.title) + ret = cvdwnld.download_cover(movie.title, output_file, True) + if ret > 0: + videos = movie.video_set.all() + for video in videos: + video.thumbnail = "/static/{}.jpeg".format(movie.title) + video.save() + + + series = Series.objects.all() + for serie in series: + output_file = "/usr/static/{}.jpeg".format(serie.title) + ret = cvdwnld.download_cover(serie.title, output_file, True) + if ret > 0: + serie.thumbnail = "/static/{}.jpeg".format(serie.title) + serie.save() + + + + + + +class Migration(migrations.Migration): + + dependencies = [ + ('StreamServerApp', '0025_video_audio_path'), + ] + + operations = [ + migrations.RunPython(code=get_cover, reverse_code=migrations.RunPython.noop), + ] diff --git a/backend/StreamServerApp/tasks.py b/backend/StreamServerApp/tasks.py index 720ea59..f50fe33 100644 --- a/backend/StreamServerApp/tasks.py +++ b/backend/StreamServerApp/tasks.py @@ -1,12 +1,13 @@ from celery import shared_task from StreamServerApp.models import Video, Series, Movie, Subtitle -import subprocess -import os +from StreamingServer.settings import STATIC_URL from django.conf import settings +from StreamServerApp.media_management.cover_downloader import cover_downloader import logging logger = logging.getLogger("root") +cvdwnld = cover_downloader() @shared_task @@ -24,3 +25,20 @@ def get_subtitles_async(video_id, video_path, remote_url): except Exception as e: logger.exception(e) return 0 + + +@shared_task +def download_cover_async(id, name, is_tv_show=False): + output_file = "/usr/static/{}.jpeg".format(name) + ret = cvdwnld.download_cover(name, output_file, is_tv_show) + if ret > 0: + video = Video.objects.get(id=id) + if is_tv_show: + serie = Series.objects.get(id=video.series_id) + serie.thumbnail = "{}{}.jpeg".format(STATIC_URL, name) + serie.save() + else: + video.thumbnail = "{}{}.jpeg".format(STATIC_URL, name) + video.save() + + return 0 diff --git a/backend/StreamServerApp/tests/tests_cover_download.py b/backend/StreamServerApp/tests/tests_cover_download.py new file mode 100644 index 0000000..34e1219 --- /dev/null +++ b/backend/StreamServerApp/tests/tests_cover_download.py @@ -0,0 +1,17 @@ +from django.test import TestCase +import os +from unittest import TestCase, mock +from StreamServerApp.media_management.cover_downloader import cover_downloader +import optparse + +class CoverDownloaderTest(TestCase): + + def setUp(self): + pass + + @mock.patch.dict(os.environ, {"TMBD_KEY": ""}) + def test_ensureErrorWithInvalidToken(self): + + cvdwnld = cover_downloader() + self.assertEqual(cvdwnld.download_cover("test", "/test/test.jpeg", True), -1) + diff --git a/backend/StreamingServer/settings.py b/backend/StreamingServer/settings.py index b072768..fad93a7 100644 --- a/backend/StreamingServer/settings.py +++ b/backend/StreamingServer/settings.py @@ -23,6 +23,8 @@ STATIC_URL = '/static/' STATIC_ROOT = '/usr/static/' +if os.getenv('DEPLOY_ENV', 'dev') == 'development': + STATIC_URL = 'http://localhost:1337/static/' ALLOWED_HOSTS = ['web', 'localhost'] diff --git a/backend/start_django_server.sh b/backend/start_django_server.sh index 5c6fdd3..6d76d54 100755 --- a/backend/start_django_server.sh +++ b/backend/start_django_server.sh @@ -1,4 +1,9 @@ service transmission-daemon start python3 /usr/src/app/manage.py collectstatic --no-input python3 /usr/src/app/manage.py clearcache -gunicorn --workers=${NUM_GUNICORN_WORKER} StreamingServer.wsgi:application --bind 0.0.0.0:8000 +if [[ "$DEPLOY_ENV" == "development" ]] +then + ./wait-for-it.sh db:5432 -- python3 /usr/src/app/manage.py runserver 0.0.0.0:8000 +else + gunicorn --workers=${NUM_GUNICORN_WORKER} StreamingServer.wsgi:application --bind 0.0.0.0:8000 +fi diff --git a/docker-compose-debug.yml b/docker-compose-debug.yml index e60de37..33f93a5 100644 --- a/docker-compose-debug.yml +++ b/docker-compose-debug.yml @@ -31,16 +31,17 @@ services: - .env environment: - DEBUG=1 + - DEPLOY_ENV=development volumes: - ./backend/:/usr/src/app/ - ./log/:/debug/ - ./Videos/:/usr/src/app/Videos - progress_volume:/usr/progress/ - - static_volume:/usr/src/app/staticfiles + - static_volume:/usr/static/ - /static/ - ipython:/root/.ipython - torrent:/usr/torrent/:rw - command: bash -c "service transmission-daemon start && ./wait-for-it.sh db:5432 -- python3 /usr/src/app/manage.py runserver 0.0.0.0:8000" + command: bash -c "./start_django_server.sh" depends_on: - db - redis @@ -50,7 +51,7 @@ services: volumes: - ./backend/:/usr/src/app/ - ./Videos/:/usr/src/app/Videos - - static_volume:/usr/src/app/staticfiles + - static_volume:/usr/static/ - torrent:/usr/torrent/:rw - secrets_volume:/secrets/ ports: @@ -79,11 +80,14 @@ services: volumes: - ./backend/:/usr/src/app/ - ./Videos/:/usr/src/app/Videos + - static_volume:/usr/static/ - progress_volume:/usr/progress/ - torrent:/usr/torrent/:rw - ./log/:/debug/ env_file: - .env + environment: + - DEPLOY_ENV=development depends_on: - db - redis diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index 2c002fa..d82069f 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -63,6 +63,7 @@ services: - ./Videos/:/usr/src/app/Videos - torrent:/usr/torrent/:rw - progress_volume:/usr/progress/ + - static_volume:/usr/static/ - ./log/:/debug/ env_file: - .env