initial commit

This commit is contained in:
herby2212 2023-01-03 06:15:00 +01:00
commit f0e91b7538
19 changed files with 101 additions and 25 deletions

View file

@ -10,14 +10,15 @@ Variable names: data {dict}
data :: Usable parameters
== Global keys ==
session_key Returns a unique session id for the active stream
session_key Returns a unique session id for the active stream.
rating_key Returns the unique identifier for the media item.
media_index Returns the index of the media item.
parent_media_index Returns the index of the media item's parent.
media_type Returns the type of session. Either 'track', 'episode' or 'movie'.
thumb Returns the location of the item's thumbnail. Use with pms_image_proxy.
bif_thumb Returns the location of the item's bif thumbnail. Use with pms_image_proxy.
art Returns the location of the item's artwork
art Returns the location of the item's artwork.
originally_available_at Returns the air date of the item.
progress_percent Returns the current progress of the item. 0 to 100.
user Returns the name of the user owning the session.
user_id Returns the Plex user id if available.
@ -62,7 +63,7 @@ DOCUMENTATION :: END
% if session is not None:
<%
from collections import defaultdict
from plexpy.helpers import cast_to_int, get_percent, page, short_season
from plexpy.helpers import cast_to_int, get_percent, page, short_season, format_date_based_show
from plexpy.common import VIDEO_RESOLUTION_OVERRIDES, AUDIO_CODEC_OVERRIDES, EXTRA_TYPES
import plexpy
%>
@ -76,6 +77,12 @@ DOCUMENTATION :: END
user_href = page('user', data['user_id']) if data['user_id'] else '#'
season = short_season(data['parent_title'])
%>
% if not data['media_index']:
<%
data['originally_available_at'] = format_date_based_show(data['originally_available_at'])
%>
% endif
<div class="dashboard-activity-instance" id="activity-instance-${sk}" data-key="${sk}" data-id="${data['session_id']}"
data-rating_key="${data['rating_key']}" data-parent_rating_key="${data['parent_rating_key']}" data-grandparent_rating_key="${data['grandparent_rating_key']}"
data-guid="${data['guid']}">
@ -499,8 +506,12 @@ DOCUMENTATION :: END
% if data['media_type'] == 'movie':
<span title="${data['year']}" class="sub-heading">${data['year']}</span>
% elif data['media_type'] == 'episode':
% if data['media_index']:
<a href="${parent_href}" title="${data['parent_title']}" class="sub-heading">${season}</a>
&middot; <a href="${href}" title="Episode ${data['media_index']}" class="sub-heading">E${data['media_index']}</a>
% else:
<a href="${href}" title="Episode ${data['originally_available_at']}" class="sub-heading">E${data['originally_available_at']}</a>
% endif
% elif data['media_type'] == 'track':
<a id="metadata-parent_title-${sk}" href="${parent_href}" title="${data['parent_title']}" class="sub-heading">${data['parent_title']}</a>
% elif data['media_type'] == 'photo':

View file

@ -41,7 +41,7 @@ DOCUMENTATION :: END
from plexpy import notifiers
from plexpy.common import MEDIA_TYPE_HEADERS, MEDIA_FLAGS_AUDIO, MEDIA_FLAGS_VIDEO
from plexpy.helpers import page, get_percent, cast_to_int, short_season
from plexpy.helpers import page, get_percent, cast_to_int, short_season, format_date_based_show
# Get audio codec file
def af(codec):
@ -81,9 +81,14 @@ DOCUMENTATION :: END
<%
data = defaultdict(lambda: None, **metadata)
media_info = defaultdict(lambda: None, **(data['media_info'][0] if data['media_info'] else {}))
episode = ''
season = ''
if data['media_type'] == 'episode':
season = short_season(data['parent_title'])
if data['media_index']:
episode = data['media_index']
else:
episode = format_date_based_show(data['originally_available_at'])
elif data['media_type'] == 'season':
season = short_season(data['title'])
%>
@ -138,7 +143,7 @@ DOCUMENTATION :: END
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li><a href="${page('info', data['parent_rating_key'])}">${data['parent_title']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
<li class="active metadata-xml">Episode ${data['media_index']} - ${data['title']}</li>
<li class="active metadata-xml">Episode ${episode} - ${data['title']}</li>
% elif data['media_type'] == 'artist':
<li><a href="${page('library', data['section_id'])}">${data['library_name']}</a></li>
<span class="breadcrumb-arrow"><i class="fa fa-chevron-right"></i></span>
@ -250,7 +255,10 @@ DOCUMENTATION :: END
<h1>${data['grandparent_title']}</h1>
<h2>${data['title']}</h2>
% if data['media_index']:
<h3 class="hidden-xs">${season} &middot; E${data['media_index']}</h3>
<h3 class="hidden-xs">${season} &middot; E${episode}</h3>
% else:
<h3 class="hidden-xs">E${episode}</h3>
% endif
% endif
% endif
% elif data['media_type'] in ('movie', 'show', 'artist', 'collection', 'playlist', 'photo_album'):
@ -261,7 +269,11 @@ DOCUMENTATION :: END
% elif data['media_type'] == 'episode':
<h1><a href="${page('info', data['grandparent_rating_key'])}">${data['grandparent_title']}</a></h1>
<h2>${data['title']}</h2>
<h3 class="hidden-xs">${season} &middot; E${data['media_index']}</h3>
% if data['media_index']:
<h3 class="hidden-xs">${season} &middot; E${episode}</h3>
% else:
<h3 class="hidden-xs">E${episode}</h3>
% endif
% elif data['media_type'] == 'album':
<h1><a href="${page('info', data['parent_rating_key'])}">${data['parent_title']}</a></h1>
<h2>${data['title']}</h2>
@ -754,9 +766,14 @@ DOCUMENTATION :: END
% if metadata:
<%
data = defaultdict(None, **metadata)
episode = ''
season = ''
if data['media_type'] == 'episode':
season = short_season(data['parent_title'])
if data['media_index']:
episode = data['media_index']
else:
episode = format_date_based_show(data['originally_available_at'])
elif data['media_type'] == 'season':
season = short_season(data['title'])
%>
@ -800,7 +817,7 @@ DOCUMENTATION :: END
% elif data['media_type'] == 'season':
${data['parent_title']}<br />${data['title']}
% elif data['media_type'] == 'episode':
${data['grandparent_title']}<br />${data['title']}<br />${season} &middot; E${data['media_index']}
${data['grandparent_title']}<br />${data['title']}<br />${season} &middot; E${episode}
% elif data['media_type'] == 'artist':
${data['title']}
% elif data['media_type'] == 'album':

View file

@ -19,6 +19,7 @@ data['children_list'] :: Usable paramaters
== Global keys ==
rating_key Returns the unique identifier for the media item.
media_index Returns the episode number.
originally_available_at Returns the air date of the item.
title Returns the name of the episode.
thumb Returns the location of the item's thumbnail. Use with pms_image_proxy.
parent_thumb Returns the location of the item's parent thumbnail. Use with pms_image_proxy.
@ -28,7 +29,7 @@ DOCUMENTATION :: END
% if data != None:
<%
from plexpy.helpers import cast_to_int, page, short_season
from plexpy.helpers import cast_to_int, page, short_season, format_date_based_show
%>
% if data['children_count'] > 0:
<div class="item-children-wrapper">
@ -166,7 +167,7 @@ DOCUMENTATION :: END
<div class="item-children-poster-face episode-item" style="background-image: url(${page('pms_image_proxy', child['thumb'], child['rating_key'], 500, 280, fallback='art')});">
<div class="item-children-card-overlay">
<div class="item-children-overlay-text">
Episode ${child['media_index'] or child['originally_available_at']}
Episode ${child['media_index'] or format_date_based_show(child['originally_available_at'])}
</div>
</div>
</div>

View file

@ -853,6 +853,10 @@ function short_season(title) {
return title
}
function format_date_based_show(date) {
return String(date).replaceAll('-', '\u00B7')
}
function loadAllBlurHash() {
$('[data-blurhash]').each(function() {
const elem = $(this);

View file

@ -179,9 +179,11 @@ history_table_options = {
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], history, rowData['live']) + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') {
rowData['originally_available_at'] = format_date_based_show(rowData['originally_available_at']);
icon = (rowData['live']) ? 'fa-broadcast-tower' : 'fa-television';
icon_title = (rowData['live']) ? 'Live TV' : 'Episode';
if (!isNaN(parseInt(rowData['parent_media_index'])) && !isNaN(parseInt(rowData['media_index']))) { parent_info = ' (' + short_season(rowData['parent_title']) + ' &middot; E' + rowData['media_index'] + ')'; }
else if (isNaN(parseInt(rowData['media_index'])) && rowData['originally_available_at']) { parent_info = ' (' + rowData['originally_available_at'] + ')'; }
else if (rowData['live'] && rowData['originally_available_at']) { parent_info = ' (' + rowData['originally_available_at'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="' + icon_title + '"><i class="fa ' + icon + ' fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';

View file

@ -112,9 +112,11 @@ history_table_modal_options = {
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key'], rowData['guid'], true, rowData['live']) + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') {
rowData['originally_available_at'] = format_date_based_show(rowData['originally_available_at']);
icon = (rowData['live']) ? 'fa-broadcast-tower' : 'fa-television';
icon_title = (rowData['live']) ? 'Live TV' : 'Episode';
if (!isNaN(parseInt(rowData['parent_media_index'])) && !isNaN(parseInt(rowData['media_index']))) { parent_info = ' (' + short_season(rowData['parent_title']) + ' &middot; E' + rowData['media_index'] + ')'; }
else if (isNaN(parseInt(rowData['media_index'])) && rowData['originally_available_at']) { parent_info = ' (' + rowData['originally_available_at'] + ')'; }
else if (rowData['live'] && rowData['originally_available_at']) { parent_info = ' (' + rowData['originally_available_at'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="' + icon_title + '"><i class="fa ' + icon + ' fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';

View file

@ -94,7 +94,9 @@ media_info_table_options = {
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key']) + '"><div style="float: left; padding-left: 15px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 500, 280, null, null, null, 'art') + '" data-height="80" data-width="140">E' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>';
thumb_popover = (rowData['media_index']) ?
'<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 500, 280, null, null, null, 'art') + '" data-height="80" data-width="140">E' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>'
: '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 500, 280, null, null, null, 'art') + '" data-height="80" data-width="140">E' + format_date_based_show(rowData['originally_available_at']) + ' - ' + rowData['title'] + '</span>';
$(td).html('<div class="history-title"><a href="' + page('info', rowData['rating_key']) + '"><div style="float: left; padding-left: 30px;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'artist') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Artist"><i class="fa fa-music fa-fw"></i></span>';

View file

@ -211,7 +211,9 @@ users_list_table_options = {
} else if (rowData['media_type'] === 'episode') {
icon = (rowData['live']) ? 'fa-broadcast-tower' : 'fa-television';
icon_title = (rowData['live']) ? 'Live TV' : 'Episode';
rowData['originally_available_at'] = format_date_based_show(rowData['originally_available_at']);
if (!isNaN(parseInt(rowData['parent_media_index'])) && !isNaN(parseInt(rowData['media_index']))) { parent_info = ' (' + short_season(rowData['parent_title']) + ' &middot; E' + rowData['media_index'] + ')'; }
else if (isNaN(parseInt(rowData['media_index'])) && rowData['originally_available_at']) { parent_info = ' (' + rowData['originally_available_at'] + ')'; }
else if (rowData['live'] && rowData['originally_available_at']) { parent_info = ' (' + rowData['originally_available_at'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="' + icon_title + '"><i class="fa ' + icon + ' fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="' + page('pms_image_proxy', rowData['thumb'], rowData['rating_key'], 300, 450, null, null, null, fallback) + '" data-height="120" data-width="80">' + cellData + parent_info + '</span>';

View file

@ -19,6 +19,7 @@ parent_title Returns the name of the artist.
grandparent_title Returns the name of the show.
media_index Returns the index number of the episode.
parent_media_index Returns the index number of the season.
originally_available_at Returns the air date of the media item.
section_id Returns the library section number of the media item.
library_name Returns the library section name of the media item.
year Returns the release year of the movie, episode, or album.
@ -32,7 +33,7 @@ DOCUMENTATION :: END
% if data:
<%
from plexpy.helpers import page, short_season
from plexpy.helpers import page, short_season, format_date_based_show
%>
<div class="dashboard-recent-media-row">
@ -78,8 +79,12 @@ DOCUMENTATION :: END
<a href="${page('info', item['rating_key'])}" title="${item['title']}">${item['title']}</a>
</h3>
<h3 class="text-muted">
% if item['media_index']:
<a href="${page('info', item['parent_rating_key'])}" title="${item['parent_title']}">${short_season(item['parent_title'])}</a>
&middot; <a href="${page('info', item['rating_key'])}" title="Episode ${item['media_index']}">E${item['media_index']}</a>
% else:
<a href="${page('info', item['rating_key'])}" title="Episode ${format_date_based_show(item['originally_available_at'])}">E${format_date_based_show(item['originally_available_at'])}</a>
% endif
</h3>
% elif item['media_type'] == 'movie':
<h3>

View file

@ -19,6 +19,7 @@ parent_title Returns the name of the artist.
grandparent_title Returns the name of the show.
media_index Returns the index number of the episode.
parent_media_index Returns the index number of the season.
originally_available_at Returns the air date of the media item.
section_id Returns the library section number of the media item.
library_name Returns the library section name of the media item.
year Returns the release year of the movie, episode, or album.
@ -32,7 +33,7 @@ DOCUMENTATION :: END
% if data != None:
<%
from plexpy.helpers import cast_to_int, page, short_season
from plexpy.helpers import cast_to_int, page, short_season, format_date_based_show
%>
% if data:
<div class="dashboard-recent-media-row">
@ -146,9 +147,14 @@ DOCUMENTATION :: END
<a href="${page('info', item['rating_key'])}" title="${item['title']}">${item['title']}</a>
</h3>
<h3 class="text-muted">
% if item['media_index']:
<a href="${page('info', item['parent_rating_key'])}" title="${item['parent_title']}">${short_season(item['parent_title'])}</a>
&middot;
<a href="${page('info', item['rating_key'])}" title="Episode ${item['media_index']}">E${item['media_index']}</a>
% else:
<a href="${page('info', item['rating_key'])}" title="Episode ${format_date_based_show(item['originally_available_at'])}">
E${format_date_based_show(item['originally_available_at'])}</a>
% endif
</h3>
</div>
% elif item['media_type'] == 'album':

View file

@ -21,6 +21,7 @@ parent_title Returns the name of the artist.
grandparent_title Returns the name of the show.
media_index Returns the index number of the episode.
parent_media_index Returns the index number of the season.
originally_available_at Returns the air date of the media item.
year Returns the release year of the movie, episode, or album.
DOCUMENTATION :: END
@ -28,7 +29,7 @@ DOCUMENTATION :: END
% if data:
<%
from plexpy.helpers import page, short_season
from plexpy.helpers import page, short_season, format_date_based_show
%>
<div class="dashboard-recent-media-row">
<div id="recently-watched-row-scroller" style="left: 0;">
@ -59,6 +60,9 @@ DOCUMENTATION :: END
</a>
<div class="dashboard-recent-media-metacontainer">
% if item['media_type'] == 'episode':
% if not item['media_index']:
item['originally_available_at'] = format_date_based_show(item['originally_available_at'])
% endif
% if item['live']:
<h3>
<a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="${item['grandparent_title']}">${item['grandparent_title']}</a>
@ -68,8 +72,12 @@ DOCUMENTATION :: END
</h3>
% if item['media_index']:
<h3 class="text-muted">
% if item['media_index']:
<a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="${item['parent_title']}">${short_season(item['parent_title'])}</a>
&middot; <a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="Episode ${item['media_index']}">E${item['media_index']}</a>
% else:
<a href="${page('info', item['rating_key'], history=True, live=item['live'])}" title="Episode ${item['originally_available_at']}">E${item['originally_available_at']}</a>
% endif
</h3>
% else:
<h3 class="text-muted">

View file

@ -1,7 +1,7 @@
% if data:
<%
import plexpy
from plexpy.helpers import grouper, get_img_service
from plexpy.helpers import grouper, get_img_service, format_date_based_show
recently_added = data['recently_added']
if plexpy.CONFIG.NEWSLETTER_SELF_HOSTED and plexpy.CONFIG.HTTP_BASE_URL:
@ -755,8 +755,11 @@
<p class="nowrap mb5" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;margin-bottom: 5px;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;max-width: 325px;color: #ffffff;">
% for i, season in enumerate(show['season'][:8]):
% if season['episode_count'] == 1:
% if season['episode'][0]['media_index']:
${season['title']} &middot; Episode ${season['episode'][0]['media_index']} - ${season['episode'][0]['title']}
% else:
${season['title']} &middot; Episode ${format_date_based_show(season['episode'][0]['originally_available_at'])}
- ${season['episode'][0]['title']}
${season['title']} &middot; Episodes ${season['episode_range']}
% endif
% if i < min(show['season_count'], 7):

View file

@ -508,6 +508,7 @@ NOTIFICATION_PARAMETERS = [
{'name': 'Season Number', 'type': 'int', 'value': 'season_num', 'description': 'The season number.', 'example': 'e.g. 1, or 1-3'},
{'name': 'Season Number 00', 'type': 'int', 'value': 'season_num00', 'description': 'The two digit season number.', 'example': 'e.g. 01, or 01-03'},
{'name': 'Episode Number', 'type': 'int', 'value': 'episode_num', 'description': 'The episode number.', 'example': 'e.g. 6, or 6-10'},
{'name': 'Episode Number', 'type': 'str', 'value': 'episode_date', 'description': 'The episode number of date based tv shows (in date format).'},
{'name': 'Episode Number 00', 'type': 'int', 'value': 'episode_num00', 'description': 'The two digit episode number.', 'example': 'e.g. 06, or 06-10'},
{'name': 'Disc Number', 'type': 'int', 'value': 'disc_num', 'description': 'The disc number.', 'example': 'e.g. 2'},
{'name': 'Disc Number 00', 'type': 'int', 'value': 'disc_num00', 'description': 'The two digit disc number.', 'example': 'e.g. 02'},

View file

@ -31,6 +31,7 @@ from collections import OrderedDict
import datetime
from functools import reduce, wraps
import hashlib
import html
import imghdr
from future.moves.itertools import islice, zip_longest
import ipwhois
@ -418,6 +419,8 @@ def clean_filename(filename, replace='_'):
cleaned_filename = ''.join(c if c in whitelist else replace for c in cleaned_filename)
return cleaned_filename
def format_date_based_show(date):
return str(date).replace('-', html.unescape('&middot;'))
def split_strip(s, delimiter=','):
return [x.strip() for x in str(s).split(delimiter) if x.strip()]

View file

@ -539,6 +539,7 @@ class Libraries(object):
# If no cache was imported, get all library children items
cached_items = {d['rating_key']: d['file_size'] for d in rows} if not refresh else {}
# TODO: Auto trigger for update - needs to be triggered to get date based episode support
if refresh or not rows:
pms_connect = pmsconnect.PmsConnect()
@ -577,6 +578,7 @@ class Libraries(object):
'year': item['year'],
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
'originally_available_at': item['originally_available_at'],
'thumb': item['thumb'],
'container': item.get('container', ''),
'bitrate': item.get('bitrate', ''),

View file

@ -766,6 +766,8 @@ class RecentlyAdded(Newsletter):
for (index, title), children in groupby(filtered_children,
key=lambda x: (x['parent_media_index'], x['parent_title'])):
episodes = list(children)
#TODO How do we want to display the episode range in the newsletter
num, num00 = format_group_index([helpers.cast_to_int(d['media_index']) for d in episodes])
seasons.append({'media_index': index,

View file

@ -912,8 +912,11 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
track_name = notify_params['title']
season_num = str(notify_params['parent_media_index']).zfill(1)
season_num00 = str(notify_params['parent_media_index']).zfill(2)
if notify_params['media_index']:
episode_num = str(notify_params['media_index']).zfill(1)
episode_num00 = str(notify_params['media_index']).zfill(2)
else:
episode_num = episode_num00 = helpers.format_date_based_show(notify_params['originally_available_at'])
disc_num = str(notify_params['parent_media_index']).zfill(1)
disc_num00 = str(notify_params['parent_media_index']).zfill(2)
track_num = str(notify_params['media_index']).zfill(1)

View file

@ -2821,6 +2821,7 @@ class PmsConnect(object):
'sort_title': helpers.get_xml_attr(item, 'titleSort'),
'media_index': helpers.get_xml_attr(item, 'index'),
'parent_media_index': helpers.get_xml_attr(item, 'parentIndex'),
'originally_available_at': helpers.get_xml_attr(item, 'originallyAvailableAt'),
'year': helpers.get_xml_attr(item, 'year'),
'thumb': helpers.get_xml_attr(item, 'thumb'),
'parent_thumb': helpers.get_xml_attr(item, 'thumb'),

View file

@ -792,6 +792,7 @@ class WebInterface(object):
"media_type": "show",
"parent_media_index": "",
"parent_rating_key": "",
"originally_available_at": "2011-08-21",
"play_count": 15,
"rating_key": "1219",
"section_id": 2,