mirror of
https://github.com/meeb/tubesync.git
synced 2025-06-23 21:46:44 +00:00
Merge branch 'handle_member_only_videos' of github.com:RichardHyde/tubesync into handle_member_only_videos
This commit is contained in:
commit
384d8a530d
3
.gitignore
vendored
3
.gitignore
vendored
@ -134,3 +134,6 @@ dmypy.json
|
|||||||
|
|
||||||
Pipfile.lock
|
Pipfile.lock
|
||||||
.vscode/launch.json
|
.vscode/launch.json
|
||||||
|
|
||||||
|
# Ignore Jetbrains IDE files
|
||||||
|
.idea/
|
16
Dockerfile
16
Dockerfile
@ -3,15 +3,15 @@ FROM debian:bookworm-slim
|
|||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
ARG S6_VERSION="3.2.0.0"
|
ARG S6_VERSION="3.2.0.2"
|
||||||
ARG SHA256_S6_AMD64="ad982a801bd72757c7b1b53539a146cf715e640b4d8f0a6a671a3d1b560fe1e2"
|
ARG SHA256_S6_AMD64="59289456ab1761e277bd456a95e737c06b03ede99158beb24f12b165a904f478"
|
||||||
ARG SHA256_S6_ARM64="868973e98210257bba725ff5b17aa092008c9a8e5174499e38ba611a8fc7e473"
|
ARG SHA256_S6_ARM64="8b22a2eaca4bf0b27a43d36e65c89d2701738f628d1abd0cea5569619f66f785"
|
||||||
ARG SHA256_S6_NOARCH="4b0c0907e6762814c31850e0e6c6762c385571d4656eb8725852b0b1586713b6"
|
ARG SHA256_S6_NOARCH="6dbcde158a3e78b9bb141d7bcb5ccb421e563523babbe2c64470e76f4fd02dae"
|
||||||
|
|
||||||
ARG FFMPEG_DATE="autobuild-2024-11-22-14-18"
|
ARG FFMPEG_DATE="autobuild-2024-12-09-14-16"
|
||||||
ARG FFMPEG_VERSION="117857-g2d077f9acd"
|
ARG FFMPEG_VERSION="118034-gd21134313f"
|
||||||
ARG SHA256_FFMPEG_AMD64="427ff38cf1e28521aac4fa7931444f6f2ff6f097f2d4a315c6f92ef1d7f90db8"
|
ARG SHA256_FFMPEG_AMD64="cd50122fb0939e913585282347a8f95074c2d5477ceb059cd90aca551f14e9ea"
|
||||||
ARG SHA256_FFMPEG_ARM64="f7ed3a50b651447477aa2637bf8da6010d8f27f8804a5d0e77b00a1d3725b27a"
|
ARG SHA256_FFMPEG_ARM64="33b4edebf9c23701473ba8db696b26072bb9b9c05fc4a156e115f94e44d361e0"
|
||||||
|
|
||||||
ENV S6_VERSION="${S6_VERSION}" \
|
ENV S6_VERSION="${S6_VERSION}" \
|
||||||
FFMPEG_DATE="${FFMPEG_DATE}" \
|
FFMPEG_DATE="${FFMPEG_DATE}" \
|
||||||
|
@ -362,20 +362,21 @@ There are a number of other environment variables you can set. These are, mostly
|
|||||||
useful if you are manually installing TubeSync in some other environment. These are:
|
useful if you are manually installing TubeSync in some other environment. These are:
|
||||||
|
|
||||||
| Name | What | Example |
|
| Name | What | Example |
|
||||||
| --------------------------- | ------------------------------------------------------------ | ------------------------------------ |
|
| ---------------------------- | ------------------------------------------------------------- |--------------------------------------|
|
||||||
| DJANGO_SECRET_KEY | Django's SECRET_KEY | YJySXnQLB7UVZw2dXKDWxI5lEZaImK6l |
|
| DJANGO_SECRET_KEY | Django's SECRET_KEY | YJySXnQLB7UVZw2dXKDWxI5lEZaImK6l |
|
||||||
| DJANGO_URL_PREFIX | Run TubeSync in a sub-URL on the web server | /somepath/ |
|
| DJANGO_URL_PREFIX | Run TubeSync in a sub-URL on the web server | /somepath/ |
|
||||||
| TUBESYNC_DEBUG | Enable debugging | True |
|
| TUBESYNC_DEBUG | Enable debugging | True |
|
||||||
| TUBESYNC_WORKERS | Number of background workers, default is 2, max allowed is 8 | 2 |
|
| TUBESYNC_WORKERS | Number of background workers, default is 2, max allowed is 8 | 2 |
|
||||||
| TUBESYNC_HOSTS | Django's ALLOWED_HOSTS, defaults to `*` | tubesync.example.com,otherhost.com |
|
| TUBESYNC_HOSTS | Django's ALLOWED_HOSTS, defaults to `*` | tubesync.example.com,otherhost.com |
|
||||||
| TUBESYNC_RESET_DOWNLOAD_DIR | Toggle resetting `/downloads` permissions, defaults to True | True |
|
| TUBESYNC_RESET_DOWNLOAD_DIR | Toggle resetting `/downloads` permissions, defaults to True | True |
|
||||||
|
| TUBESYNC_VIDEO_HEIGHT_CUTOFF | Smallest video height in pixels permitted to download | 240 |
|
||||||
|
| TUBESYNC_DIRECTORY_PREFIX | Enable `video` and `audio` directory prefixes in `/downloads` | True |
|
||||||
| GUNICORN_WORKERS | Number of gunicorn workers to spawn | 3 |
|
| GUNICORN_WORKERS | Number of gunicorn workers to spawn | 3 |
|
||||||
| LISTEN_HOST | IP address for gunicorn to listen on | 127.0.0.1 |
|
| LISTEN_HOST | IP address for gunicorn to listen on | 127.0.0.1 |
|
||||||
| LISTEN_PORT | Port number for gunicorn to listen on | 8080 |
|
| LISTEN_PORT | Port number for gunicorn to listen on | 8080 |
|
||||||
| HTTP_USER | Sets the username for HTTP basic authentication | some-username |
|
| HTTP_USER | Sets the username for HTTP basic authentication | some-username |
|
||||||
| HTTP_PASS | Sets the password for HTTP basic authentication | some-secure-password |
|
| HTTP_PASS | Sets the password for HTTP basic authentication | some-secure-password |
|
||||||
| DATABASE_CONNECTION | Optional external database connection details | mysql://user:pass@host:port/database |
|
| DATABASE_CONNECTION | Optional external database connection details | mysql://user:pass@host:port/database |
|
||||||
| VIDEO_HEIGHT_CUTOFF | Smallest video height in pixels permitted to download | 240 |
|
|
||||||
|
|
||||||
|
|
||||||
# Manual, non-containerised, installation
|
# Manual, non-containerised, installation
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
from common.logger import log
|
from common.logger import log
|
||||||
from .models import Media
|
from .models import Media
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from .overrides.custom_filter import filter_custom
|
from .overrides.custom_filter import filter_custom
|
||||||
|
|
||||||
@ -127,19 +127,15 @@ def filter_max_cap(instance: Media):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# If the source has a cut-off, check the upload date is within the allowed delta
|
# If the source has a cut-off, check the download date is within the allowed delta
|
||||||
def filter_source_cutoff(instance: Media):
|
def filter_source_cutoff(instance: Media):
|
||||||
if instance.source.delete_old_media and instance.source.days_to_keep > 0:
|
if instance.source.delete_old_media and instance.source.days_to_keep_date:
|
||||||
if not isinstance(instance.published, datetime):
|
if not instance.downloaded or not isinstance(instance.download_date, datetime):
|
||||||
# Media has no known published date or incomplete metadata
|
return False
|
||||||
log.info(
|
|
||||||
f"Media: {instance.source} / {instance} has no published date, skipping"
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
delta = timezone.now() - timedelta(days=instance.source.days_to_keep)
|
days_to_keep_age = instance.source.days_to_keep_date
|
||||||
if instance.published < delta:
|
if instance.download_date < days_to_keep_age:
|
||||||
# Media was published after the cutoff date, skip it
|
# Media has expired, skip it
|
||||||
log.info(
|
log.info(
|
||||||
f"Media: {instance.source} / {instance} is older than "
|
f"Media: {instance.source} / {instance} is older than "
|
||||||
f"{instance.source.days_to_keep} days, skipping"
|
f"{instance.source.days_to_keep} days, skipping"
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
from .utils import multi_key_sort
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
@ -21,7 +22,7 @@ def get_best_combined_format(media):
|
|||||||
'''
|
'''
|
||||||
for fmt in media.iter_formats():
|
for fmt in media.iter_formats():
|
||||||
# Check height matches
|
# Check height matches
|
||||||
if media.source.source_resolution.strip().upper() != fmt['format']:
|
if media.source.source_resolution_height != fmt['height']:
|
||||||
continue
|
continue
|
||||||
# Check the video codec matches
|
# Check the video codec matches
|
||||||
if media.source.source_vcodec != fmt['vcodec']:
|
if media.source.source_vcodec != fmt['vcodec']:
|
||||||
@ -47,7 +48,7 @@ def get_best_audio_format(media):
|
|||||||
Finds the best match for the source required audio format. If the source
|
Finds the best match for the source required audio format. If the source
|
||||||
has a 'fallback' of fail this can return no match.
|
has a 'fallback' of fail this can return no match.
|
||||||
'''
|
'''
|
||||||
# Order all audio-only formats by bitrate
|
# Reverse order all audio-only formats
|
||||||
audio_formats = []
|
audio_formats = []
|
||||||
for fmt in media.iter_formats():
|
for fmt in media.iter_formats():
|
||||||
# If the format has a video stream, skip it
|
# If the format has a video stream, skip it
|
||||||
@ -56,18 +57,18 @@ def get_best_audio_format(media):
|
|||||||
if not fmt['acodec']:
|
if not fmt['acodec']:
|
||||||
continue
|
continue
|
||||||
audio_formats.append(fmt)
|
audio_formats.append(fmt)
|
||||||
audio_formats = list(reversed(sorted(audio_formats, key=lambda k: k['abr'])))
|
|
||||||
if not audio_formats:
|
if not audio_formats:
|
||||||
# Media has no audio formats at all
|
# Media has no audio formats at all
|
||||||
return False, False
|
return False, False
|
||||||
# Find the highest bitrate audio format with a matching codec
|
audio_formats = list(reversed(audio_formats))
|
||||||
|
# Find the first audio format with a matching codec
|
||||||
for fmt in audio_formats:
|
for fmt in audio_formats:
|
||||||
if media.source.source_acodec == fmt['acodec']:
|
if media.source.source_acodec == fmt['acodec']:
|
||||||
# Matched!
|
# Matched!
|
||||||
return True, fmt['id']
|
return True, fmt['id']
|
||||||
# No codecs matched
|
# No codecs matched
|
||||||
if media.source.can_fallback:
|
if media.source.can_fallback:
|
||||||
# Can fallback, find the next highest bitrate non-matching codec
|
# Can fallback, find the next non-matching codec
|
||||||
return False, audio_formats[0]['id']
|
return False, audio_formats[0]['id']
|
||||||
else:
|
else:
|
||||||
# Can't fallback
|
# Can't fallback
|
||||||
@ -86,6 +87,7 @@ def get_best_video_format(media):
|
|||||||
return False, False
|
return False, False
|
||||||
# Filter video-only formats by resolution that matches the source
|
# Filter video-only formats by resolution that matches the source
|
||||||
video_formats = []
|
video_formats = []
|
||||||
|
sort_keys = [('height', False), ('vcodec', True), ('vbr', False)] # key, reverse
|
||||||
for fmt in media.iter_formats():
|
for fmt in media.iter_formats():
|
||||||
# If the format has an audio stream, skip it
|
# If the format has an audio stream, skip it
|
||||||
if fmt['acodec'] is not None:
|
if fmt['acodec'] is not None:
|
||||||
@ -94,6 +96,8 @@ def get_best_video_format(media):
|
|||||||
continue
|
continue
|
||||||
if media.source.source_resolution.strip().upper() == fmt['format']:
|
if media.source.source_resolution.strip().upper() == fmt['format']:
|
||||||
video_formats.append(fmt)
|
video_formats.append(fmt)
|
||||||
|
elif media.source.source_resolution_height == fmt['height']:
|
||||||
|
video_formats.append(fmt)
|
||||||
# Check we matched some streams
|
# Check we matched some streams
|
||||||
if not video_formats:
|
if not video_formats:
|
||||||
# No streams match the requested resolution, see if we can fallback
|
# No streams match the requested resolution, see if we can fallback
|
||||||
@ -109,13 +113,17 @@ def get_best_video_format(media):
|
|||||||
else:
|
else:
|
||||||
# Can't fallback
|
# Can't fallback
|
||||||
return False, False
|
return False, False
|
||||||
video_formats = list(reversed(sorted(video_formats, key=lambda k: k['height'])))
|
|
||||||
source_resolution = media.source.source_resolution.strip().upper()
|
|
||||||
source_vcodec = media.source.source_vcodec
|
|
||||||
if not video_formats:
|
if not video_formats:
|
||||||
# Still no matches
|
# Still no matches
|
||||||
return False, False
|
return False, False
|
||||||
|
video_formats = multi_key_sort(video_formats, sort_keys, True)
|
||||||
|
source_resolution = media.source.source_resolution.strip().upper()
|
||||||
|
source_vcodec = media.source.source_vcodec
|
||||||
exact_match, best_match = None, None
|
exact_match, best_match = None, None
|
||||||
|
for fmt in video_formats:
|
||||||
|
# format_note was blank, match height instead
|
||||||
|
if '' == fmt['format'] and fmt['height'] == media.source.source_resolution_height:
|
||||||
|
fmt['format'] = source_resolution
|
||||||
# Of our filtered video formats, check for resolution + codec + hdr + fps match
|
# Of our filtered video formats, check for resolution + codec + hdr + fps match
|
||||||
if media.source.prefer_60fps and media.source.prefer_hdr:
|
if media.source.prefer_60fps and media.source.prefer_hdr:
|
||||||
for fmt in video_formats:
|
for fmt in video_formats:
|
||||||
@ -331,7 +339,7 @@ def get_best_video_format(media):
|
|||||||
for fmt in video_formats:
|
for fmt in video_formats:
|
||||||
# Check for a codec, hdr and fps match but drop the resolution
|
# Check for a codec, hdr and fps match but drop the resolution
|
||||||
if (source_vcodec == fmt['vcodec'] and
|
if (source_vcodec == fmt['vcodec'] and
|
||||||
not fmt['is_hdr'] and fmt['is_60fps']):
|
not fmt['is_hdr'] and not fmt['is_60fps']):
|
||||||
# Close match
|
# Close match
|
||||||
exact_match, best_match = False, fmt
|
exact_match, best_match = False, fmt
|
||||||
break
|
break
|
||||||
|
19
tubesync/sync/migrations/0026_alter_source_sub_langs.py
Normal file
19
tubesync/sync/migrations/0026_alter_source_sub_langs.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.2.25 on 2024-12-11 12:43
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('sync', '0025_add_video_type_support'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='source',
|
||||||
|
name='sub_langs',
|
||||||
|
field=models.CharField(default='en', help_text='List of subtitles langs to download, comma-separated. Example: en,fr or all,-fr,-live_chat', max_length=30, validators=[django.core.validators.RegexValidator(message='Subtitle langs must be a comma-separated list of langs. example: en,fr or all,-fr,-live_chat', regex='^(\\-?[\\_\\.a-zA-Z-]+(,|$))+')], verbose_name='subs langs'),
|
||||||
|
),
|
||||||
|
]
|
@ -422,7 +422,7 @@ class Source(models.Model):
|
|||||||
help_text=_('List of subtitles langs to download, comma-separated. Example: en,fr or all,-fr,-live_chat'),
|
help_text=_('List of subtitles langs to download, comma-separated. Example: en,fr or all,-fr,-live_chat'),
|
||||||
validators=[
|
validators=[
|
||||||
RegexValidator(
|
RegexValidator(
|
||||||
regex=r"^(\-?[\_\.a-zA-Z]+,)*(\-?[\_\.a-zA-Z]+){1}$",
|
regex=r"^(\-?[\_\.a-zA-Z-]+(,|$))+",
|
||||||
message=_('Subtitle langs must be a comma-separated list of langs. example: en,fr or all,-fr,-live_chat')
|
message=_('Subtitle langs must be a comma-separated list of langs. example: en,fr or all,-fr,-live_chat')
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -460,6 +460,14 @@ class Source(models.Model):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def days_to_keep_date(self):
|
||||||
|
delta = self.days_to_keep
|
||||||
|
if delta > 0:
|
||||||
|
return timezone.now() - timedelta(days=delta)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extension(self):
|
def extension(self):
|
||||||
'''
|
'''
|
||||||
@ -514,10 +522,13 @@ class Source(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def type_directory_path(self):
|
def type_directory_path(self):
|
||||||
|
if settings.SOURCE_DOWNLOAD_DIRECTORY_PREFIX:
|
||||||
if self.source_resolution == self.SOURCE_RESOLUTION_AUDIO:
|
if self.source_resolution == self.SOURCE_RESOLUTION_AUDIO:
|
||||||
return Path(settings.DOWNLOAD_AUDIO_DIR) / self.directory
|
return Path(settings.DOWNLOAD_AUDIO_DIR) / self.directory
|
||||||
else:
|
else:
|
||||||
return Path(settings.DOWNLOAD_VIDEO_DIR) / self.directory
|
return Path(settings.DOWNLOAD_VIDEO_DIR) / self.directory
|
||||||
|
else:
|
||||||
|
return Path(self.directory)
|
||||||
|
|
||||||
def make_directory(self):
|
def make_directory(self):
|
||||||
return os.makedirs(self.directory_path, exist_ok=True)
|
return os.makedirs(self.directory_path, exist_ok=True)
|
||||||
@ -1291,10 +1302,7 @@ class Media(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def directory_path(self):
|
def directory_path(self):
|
||||||
# Otherwise, create a suitable filename from the source media_format
|
dirname = self.source.directory_path / self.filename
|
||||||
media_format = str(self.source.media_format)
|
|
||||||
media_details = self.format_dict
|
|
||||||
dirname = self.source.directory_path / media_format.format(**media_details)
|
|
||||||
return os.path.dirname(str(dirname))
|
return os.path.dirname(str(dirname))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from pathlib import Path
|
||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlsplit
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -626,6 +628,25 @@ class FilepathTestCase(TestCase):
|
|||||||
('no-fancy-stuff-title_test_720p-720x1280-opus'
|
('no-fancy-stuff-title_test_720p-720x1280-opus'
|
||||||
'-vp9-30fps-hdr.mkv'))
|
'-vp9-30fps-hdr.mkv'))
|
||||||
|
|
||||||
|
def test_directory_prefix(self):
|
||||||
|
# Confirm the setting exists and is valid
|
||||||
|
self.assertTrue(hasattr(settings, 'SOURCE_DOWNLOAD_DIRECTORY_PREFIX'))
|
||||||
|
self.assertTrue(isinstance(settings.SOURCE_DOWNLOAD_DIRECTORY_PREFIX, bool))
|
||||||
|
# Test the default behavior for "True", forced "audio" or "video" parent directories for sources
|
||||||
|
settings.SOURCE_DOWNLOAD_DIRECTORY_PREFIX = True
|
||||||
|
self.source.source_resolution = Source.SOURCE_RESOLUTION_AUDIO
|
||||||
|
test_audio_prefix_path = Path(self.source.directory_path)
|
||||||
|
self.assertEqual(test_audio_prefix_path.parts[-2], 'audio')
|
||||||
|
self.assertEqual(test_audio_prefix_path.parts[-1], 'testdirectory')
|
||||||
|
self.source.source_resolution = Source.SOURCE_RESOLUTION_1080P
|
||||||
|
test_video_prefix_path = Path(self.source.directory_path)
|
||||||
|
self.assertEqual(test_video_prefix_path.parts[-2], 'video')
|
||||||
|
self.assertEqual(test_video_prefix_path.parts[-1], 'testdirectory')
|
||||||
|
# Test the default behavior for "False", no parent directories for sources
|
||||||
|
settings.SOURCE_DOWNLOAD_DIRECTORY_PREFIX = False
|
||||||
|
test_no_prefix_path = Path(self.source.directory_path)
|
||||||
|
self.assertEqual(test_no_prefix_path.parts[-1], 'testdirectory')
|
||||||
|
|
||||||
|
|
||||||
class MediaTestCase(TestCase):
|
class MediaTestCase(TestCase):
|
||||||
|
|
||||||
@ -1687,7 +1708,9 @@ class FormatMatchingTestCase(TestCase):
|
|||||||
msg=f'Media title "{self.media.title}" checked against regex "{self.source.filter_text}" failed '
|
msg=f'Media title "{self.media.title}" checked against regex "{self.source.filter_text}" failed '
|
||||||
f'expected {expected_match_result}')
|
f'expected {expected_match_result}')
|
||||||
|
|
||||||
|
|
||||||
class TasksTestCase(TestCase):
|
class TasksTestCase(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# Disable general logging for test case
|
# Disable general logging for test case
|
||||||
logging.disable(logging.CRITICAL)
|
logging.disable(logging.CRITICAL)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import math
|
import math
|
||||||
|
from operator import itemgetter
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import requests
|
import requests
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
@ -134,6 +135,30 @@ def seconds_to_timestr(seconds):
|
|||||||
return '{:02d}:{:02d}:{:02d}'.format(hour, minutes, seconds)
|
return '{:02d}:{:02d}:{:02d}'.format(hour, minutes, seconds)
|
||||||
|
|
||||||
|
|
||||||
|
def multi_key_sort(sort_dict, specs, use_reversed=False):
|
||||||
|
result = list(sort_dict)
|
||||||
|
for key, reverse in reversed(specs):
|
||||||
|
result = sorted(result, key=itemgetter(key), reverse=reverse)
|
||||||
|
if use_reversed:
|
||||||
|
return list(reversed(result))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_codec(codec_str):
|
||||||
|
result = str(codec_str).upper()
|
||||||
|
parts = result.split('.')
|
||||||
|
if len(parts) > 0:
|
||||||
|
result = parts[0].strip()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
if 'NONE' == result:
|
||||||
|
return None
|
||||||
|
if str(0) in result:
|
||||||
|
prefix = result.rstrip('0123456789')
|
||||||
|
result = prefix + str(int(result[len(prefix):]))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def parse_media_format(format_dict):
|
def parse_media_format(format_dict):
|
||||||
'''
|
'''
|
||||||
This parser primarily adapts the format dict returned by youtube-dl into a
|
This parser primarily adapts the format dict returned by youtube-dl into a
|
||||||
@ -141,21 +166,9 @@ def parse_media_format(format_dict):
|
|||||||
any internals, update it here.
|
any internals, update it here.
|
||||||
'''
|
'''
|
||||||
vcodec_full = format_dict.get('vcodec', '')
|
vcodec_full = format_dict.get('vcodec', '')
|
||||||
vcodec_parts = vcodec_full.split('.')
|
vcodec = normalize_codec(vcodec_full)
|
||||||
if len(vcodec_parts) > 0:
|
|
||||||
vcodec = vcodec_parts[0].strip().upper()
|
|
||||||
else:
|
|
||||||
vcodec = None
|
|
||||||
if vcodec == 'NONE':
|
|
||||||
vcodec = None
|
|
||||||
acodec_full = format_dict.get('acodec', '')
|
acodec_full = format_dict.get('acodec', '')
|
||||||
acodec_parts = acodec_full.split('.')
|
acodec = normalize_codec(acodec_full)
|
||||||
if len(acodec_parts) > 0:
|
|
||||||
acodec = acodec_parts[0].strip().upper()
|
|
||||||
else:
|
|
||||||
acodec = None
|
|
||||||
if acodec == 'NONE':
|
|
||||||
acodec = None
|
|
||||||
try:
|
try:
|
||||||
fps = int(format_dict.get('fps', 0))
|
fps = int(format_dict.get('fps', 0))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
|
@ -81,3 +81,10 @@ if BASICAUTH_USERNAME and BASICAUTH_PASSWORD:
|
|||||||
else:
|
else:
|
||||||
BASICAUTH_DISABLE = True
|
BASICAUTH_DISABLE = True
|
||||||
BASICAUTH_USERS = {}
|
BASICAUTH_USERS = {}
|
||||||
|
|
||||||
|
|
||||||
|
SOURCE_DOWNLOAD_DIRECTORY_PREFIX_STR = os.getenv('TUBESYNC_DIRECTORY_PREFIX', 'True').strip().lower()
|
||||||
|
SOURCE_DOWNLOAD_DIRECTORY_PREFIX = True if SOURCE_DOWNLOAD_DIRECTORY_PREFIX_STR == 'true' else False
|
||||||
|
|
||||||
|
|
||||||
|
VIDEO_HEIGHT_CUTOFF = int(os.getenv("TUBESYNC_VIDEO_HEIGHT_CUTOFF", "240"))
|
||||||
|
@ -150,10 +150,18 @@ MEDIA_THUMBNAIL_WIDTH = 430 # Width in pixels to resize thumbnai
|
|||||||
MEDIA_THUMBNAIL_HEIGHT = 240 # Height in pixels to resize thumbnails to
|
MEDIA_THUMBNAIL_HEIGHT = 240 # Height in pixels to resize thumbnails to
|
||||||
|
|
||||||
|
|
||||||
VIDEO_HEIGHT_CUTOFF = int(os.getenv("VIDEO_HEIGHT_CUTOFF", "240")) # Smallest resolution in pixels permitted to download
|
VIDEO_HEIGHT_CUTOFF = 240 # Smallest resolution in pixels permitted to download
|
||||||
VIDEO_HEIGHT_IS_HD = 500 # Height in pixels to count as 'HD'
|
VIDEO_HEIGHT_IS_HD = 500 # Height in pixels to count as 'HD'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# If True source directories are prefixed with their type (either 'video' or 'audio')
|
||||||
|
# e.g. /downloads/video/SomeSourceName
|
||||||
|
# If False, sources are placed directly in /downloads
|
||||||
|
# e.g. /downloads/SomeSourceName
|
||||||
|
SOURCE_DOWNLOAD_DIRECTORY_PREFIX = True
|
||||||
|
|
||||||
|
|
||||||
YOUTUBE_DL_CACHEDIR = None
|
YOUTUBE_DL_CACHEDIR = None
|
||||||
YOUTUBE_DL_TEMPDIR = None
|
YOUTUBE_DL_TEMPDIR = None
|
||||||
YOUTUBE_DEFAULTS = {
|
YOUTUBE_DEFAULTS = {
|
||||||
|
Loading…
Reference in New Issue
Block a user