+ {% if not video.watched %}
+ New
+ {% endif %}
+ {{ video.name }}
+
+
{{ video.description | truncatechars:120 }}
+
+
+
+
+ {% endfor %}
+
+
\ No newline at end of file
diff --git a/YtManagerApp/urls.py b/YtManagerApp/urls.py
new file mode 100644
index 0000000..e69de29
diff --git a/YtManagerApp/utils/__init__.py b/YtManagerApp/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/YtManagerApp/utils/customconfigparser.py b/YtManagerApp/utils/customconfigparser.py
new file mode 100644
index 0000000..ebc0706
--- /dev/null
+++ b/YtManagerApp/utils/customconfigparser.py
@@ -0,0 +1,114 @@
+import os
+import os.path
+import re
+from configparser import Interpolation, NoSectionError, NoOptionError, InterpolationMissingOptionError, \
+ InterpolationDepthError, InterpolationSyntaxError, ConfigParser
+
+MAX_INTERPOLATION_DEPTH = 10
+
+
+class ExtendedInterpolatorWithEnv(Interpolation):
+ """Advanced variant of interpolation, supports the syntax used by
+ `zc.buildout'. Enables interpolation between sections.
+
+ This modified version also allows specifying environment variables
+ using ${env:...}, and allows adding additional options using 'set_additional_options'. """
+
+ _KEYCRE = re.compile(r"\$\{([^}]+)\}")
+
+ def __init__(self, **kwargs):
+ self.__kwargs = kwargs
+
+ def set_additional_options(self, **kwargs):
+ self.__kwargs = kwargs
+
+ def before_get(self, parser, section, option, value, defaults):
+ L = []
+ self._interpolate_some(parser, option, L, value, section, defaults, 1)
+ return ''.join(L)
+
+ def before_set(self, parser, section, option, value):
+ tmp_value = value.replace('$$', '') # escaped dollar signs
+ tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
+ if '$' in tmp_value:
+ raise ValueError("invalid interpolation syntax in %r at "
+ "position %d" % (value, tmp_value.find('$')))
+ return value
+
+ def _resolve_option(self, option, defaults):
+ if option in self.__kwargs:
+ return self.__kwargs[option]
+ return defaults[option]
+
+ def _resolve_section_option(self, section, option, parser):
+ if section == 'env':
+ return os.getenv(option, '')
+ return parser.get(section, option, raw=True)
+
+ def _interpolate_some(self, parser, option, accum, rest, section, map,
+ depth):
+ rawval = parser.get(section, option, raw=True, fallback=rest)
+ if depth > MAX_INTERPOLATION_DEPTH:
+ raise InterpolationDepthError(option, section, rawval)
+ while rest:
+ p = rest.find("$")
+ if p < 0:
+ accum.append(rest)
+ return
+ if p > 0:
+ accum.append(rest[:p])
+ rest = rest[p:]
+ # p is no longer used
+ c = rest[1:2]
+ if c == "$":
+ accum.append("$")
+ rest = rest[2:]
+ elif c == "{":
+ m = self._KEYCRE.match(rest)
+ if m is None:
+ raise InterpolationSyntaxError(option, section,
+ "bad interpolation variable reference %r" % rest)
+ path = m.group(1).split(':')
+ rest = rest[m.end():]
+ sect = section
+ opt = option
+ try:
+ if len(path) == 1:
+ opt = parser.optionxform(path[0])
+ v = self._resolve_option(opt, map)
+ elif len(path) == 2:
+ sect = path[0]
+ opt = parser.optionxform(path[1])
+ v = self._resolve_section_option(sect, opt, parser)
+ else:
+ raise InterpolationSyntaxError(
+ option, section,
+ "More than one ':' found: %r" % (rest,))
+ except (KeyError, NoSectionError, NoOptionError):
+ raise InterpolationMissingOptionError(
+ option, section, rawval, ":".join(path)) from None
+ if "$" in v:
+ self._interpolate_some(parser, opt, accum, v, sect,
+ dict(parser.items(sect, raw=True)),
+ depth + 1)
+ else:
+ accum.append(v)
+ else:
+ raise InterpolationSyntaxError(
+ option, section,
+ "'$' must be followed by '$' or '{', "
+ "found: %r" % (rest,))
+
+
+class ConfigParserWithEnv(ConfigParser):
+ _DEFAULT_INTERPOLATION = ExtendedInterpolatorWithEnv()
+
+ def set_additional_interpolation_options(self, **kwargs):
+ """
+ Sets additional options to be used in interpolation.
+ Only works with ExtendedInterpolatorWithEnv
+ :param kwargs:
+ :return:
+ """
+ if isinstance(super()._interpolation, ExtendedInterpolatorWithEnv):
+ super()._interpolation.set_additional_options(**kwargs)
diff --git a/YtManagerApp/youtube.py b/YtManagerApp/utils/youtube.py
similarity index 100%
rename from YtManagerApp/youtube.py
rename to YtManagerApp/utils/youtube.py
diff --git a/YtManagerApp/views.py b/YtManagerApp/views.py
index 296841a..d3d76eb 100644
--- a/YtManagerApp/views.py
+++ b/YtManagerApp/views.py
@@ -20,7 +20,8 @@ def get_children_recurse(parent_id):
children.append({
"id": "sub" + str(sub.id),
"type": "sub",
- "text": sub.name
+ "text": sub.name,
+ "icon": sub.icon_default
})
return children
@@ -82,6 +83,21 @@ def ajax_delete_subscription(request: HttpRequest, sid):
return HttpResponse()
+def ajax_list_videos(request: HttpRequest):
+ if request.method == 'POST':
+ type = request.POST['type']
+ id = request.POST['id']
+ context = {}
+
+ if type == 'sub':
+ context['videos'] = SubscriptionManager.list_videos(int(id))
+ else:
+ context['videos'] = FolderManager.list_videos(int(id))
+
+ return render(request, 'YtManagerApp/main_videos.html', context)
+
+
def index(request: HttpRequest):
context = {}
return render(request, 'YtManagerApp/index.html', context)
+
diff --git a/config/config.ini b/config/config.ini
new file mode 100644
index 0000000..3237138
--- /dev/null
+++ b/config/config.ini
@@ -0,0 +1,61 @@
+; Use $ to use the value of an environment variable.
+; The global section contains settings that apply to the entire server
+[global]
+; YouTube API key - get this from your user account
+YoutubeApiKey=AIzaSyBabzE4Bup77WexdLMa9rN9z-wJidEfNX8
+
+; Specifies the synchronization schedule, in crontab format.
+; Format:
+SynchronizationSchedule=0 * * * * *
+
+; Number of threads running the scheduler
+; Since most of the jobs scheduled are downloads, there is no advantage to having
+; a higher concurrency
+SchedulerConcurrency=2
+
+; Log level
+LogLevel=DEBUG
+
+; Default user settings
+[user]
+; When a video is deleted on the system, it will be marked as 'watched'
+MarkDeletedAsWatched=True
+
+; Videos marked as watched are automatically deleted
+DeleteWatched=True
+
+; Enable automatic downloading
+AutoDownload=True
+
+; Limit the total number of videos downloaded (-1 or empty = no limit)
+DownloadGlobalLimit=
+
+; Limit the numbers of videos per subscription (-1 or empty = no limit)
+DownloadSubscriptionLimit=5
+
+; Number of download attempts
+DownloadMaxAttempts=3
+
+; Download order
+; Options: playlist_index, publish_date, name.
+; Use - to reverse order (e.g. -publish_date means to order by publish date descending)
+DownloadOrder=playlist_index
+
+; Path where downloaded videos are stored
+;DownloadPath=${env:USERPROFILE}${env:HOME}/Downloads
+DownloadPath=D:\\Dev\\youtube-channel-manager\\temp\\download
+
+; A pattern which describes how downloaded files are organized. Extensions are automatically appended.
+; Supported fields: channel, channel_id, playlist, playlist_id, playlist_index, title, id
+; The default pattern should work pretty well with Plex
+DownloadFilePattern=${channel}/${playlist}/S01E${playlist_index} - ${title} [${id}]
+
+; Download format that will be passed to youtube-dl. See the youtube-dl documentation for more details.
+DownloadFormat=bestvideo+bestaudio
+
+; Subtitles - these options match the youtube-dl options
+DownloadSubtitles=True
+DownloadAutogeneratedSubtitles=False
+DownloadSubtitlesAll=False
+DownloadSubtitlesLangs=en,ro
+DownloadSubtitlesFormat=
diff --git a/config/config.ini.default b/config/config.ini.default
new file mode 100644
index 0000000..3237138
--- /dev/null
+++ b/config/config.ini.default
@@ -0,0 +1,61 @@
+; Use $ to use the value of an environment variable.
+; The global section contains settings that apply to the entire server
+[global]
+; YouTube API key - get this from your user account
+YoutubeApiKey=AIzaSyBabzE4Bup77WexdLMa9rN9z-wJidEfNX8
+
+; Specifies the synchronization schedule, in crontab format.
+; Format:
+SynchronizationSchedule=0 * * * * *
+
+; Number of threads running the scheduler
+; Since most of the jobs scheduled are downloads, there is no advantage to having
+; a higher concurrency
+SchedulerConcurrency=2
+
+; Log level
+LogLevel=DEBUG
+
+; Default user settings
+[user]
+; When a video is deleted on the system, it will be marked as 'watched'
+MarkDeletedAsWatched=True
+
+; Videos marked as watched are automatically deleted
+DeleteWatched=True
+
+; Enable automatic downloading
+AutoDownload=True
+
+; Limit the total number of videos downloaded (-1 or empty = no limit)
+DownloadGlobalLimit=
+
+; Limit the numbers of videos per subscription (-1 or empty = no limit)
+DownloadSubscriptionLimit=5
+
+; Number of download attempts
+DownloadMaxAttempts=3
+
+; Download order
+; Options: playlist_index, publish_date, name.
+; Use - to reverse order (e.g. -publish_date means to order by publish date descending)
+DownloadOrder=playlist_index
+
+; Path where downloaded videos are stored
+;DownloadPath=${env:USERPROFILE}${env:HOME}/Downloads
+DownloadPath=D:\\Dev\\youtube-channel-manager\\temp\\download
+
+; A pattern which describes how downloaded files are organized. Extensions are automatically appended.
+; Supported fields: channel, channel_id, playlist, playlist_id, playlist_index, title, id
+; The default pattern should work pretty well with Plex
+DownloadFilePattern=${channel}/${playlist}/S01E${playlist_index} - ${title} [${id}]
+
+; Download format that will be passed to youtube-dl. See the youtube-dl documentation for more details.
+DownloadFormat=bestvideo+bestaudio
+
+; Subtitles - these options match the youtube-dl options
+DownloadSubtitles=True
+DownloadAutogeneratedSubtitles=False
+DownloadSubtitlesAll=False
+DownloadSubtitlesLangs=en,ro
+DownloadSubtitlesFormat=