mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-08 14:10:52 -07:00
Add m3u8 export file format
This commit is contained in:
parent
be9f06795d
commit
34c9ede9c9
6 changed files with 89 additions and 17 deletions
|
@ -3491,6 +3491,9 @@ pre::-webkit-scrollbar-thumb {
|
|||
.selectize-input input[type='text'] {
|
||||
height: 20px;
|
||||
}
|
||||
.selectize-input.disabled, .selectize-input.disabled * {
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
.small-muted {
|
||||
font-size: small;
|
||||
color: #777;
|
||||
|
|
|
@ -79,10 +79,10 @@ DOCUMENTATION :: END
|
|||
<p class="help-block">Add additional fields to the selected media info export level.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="file_format_select">File Format</label>
|
||||
<label for="export_file_format">File Format</label>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<select class="form-control" id="file_format_select" name="file_format_select">
|
||||
<select class="form-control" id="export_file_format" name="export_file_format">
|
||||
% for format in file_formats:
|
||||
<option value="${format}">${format.upper()}</option>
|
||||
% endfor
|
||||
|
@ -184,13 +184,31 @@ DOCUMENTATION :: END
|
|||
}
|
||||
getExportFields();
|
||||
|
||||
$('#export_file_format').on('change', function() {
|
||||
if ($(this).val() === 'm3u8') {
|
||||
$('#metadata_export_level_select').prop('disabled', true);
|
||||
$('#media_info_export_level_select').prop('disabled', true);
|
||||
$("#export_include_thumb").prop('disabled', true);
|
||||
$("#export_include_art").prop('disabled', true);
|
||||
export_custom_metadata_fields.disable();
|
||||
export_custom_media_info_fields.disable();
|
||||
} else {
|
||||
$('#metadata_export_level_select').prop('disabled', false);
|
||||
$('#media_info_export_level_select').prop('disabled', false);
|
||||
$("#export_include_thumb").prop('disabled', false);
|
||||
$("#export_include_art").prop('disabled', false);
|
||||
export_custom_metadata_fields.enable();
|
||||
export_custom_media_info_fields.enable();
|
||||
}
|
||||
})
|
||||
|
||||
$("#export_metadata").click(function() {
|
||||
var section_id = $('#export_section_id').val();
|
||||
var user_id = $('#export_user_id').val();
|
||||
var rating_key = $('#export_rating_key').val();
|
||||
var metadata_export_level = $('#metadata_export_level_select option:selected').val();
|
||||
var media_info_export_level = $('#media_info_export_level_select option:selected').val();
|
||||
var file_format = $('#file_format_select option:selected').val();
|
||||
var file_format = $('#export_file_format option:selected').val();
|
||||
var include_thumb = $("#export_include_thumb").is(':checked') ? 1 : 0;
|
||||
var include_art = $("#export_include_art").is(':checked') ? 1 : 0;
|
||||
var custom_fields = [
|
||||
|
|
|
@ -989,14 +989,14 @@ DOCUMENTATION :: END
|
|||
clearSearchButton('export_table-RK-${data["rating_key"]}', export_table);
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
if ($('#tabs-history').length) {
|
||||
$('a[href="#tabs-export"]').on('shown.bs.tab', function() {
|
||||
if (typeof(export_table) === 'undefined') {
|
||||
loadExportTable();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
$(document).ready(function () {
|
||||
if (!($('#tabs-history').length)) {
|
||||
loadExportTable();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -91,7 +91,7 @@ class Export(object):
|
|||
}
|
||||
METADATA_LEVELS = (0, 1, 2, 3, 9)
|
||||
MEDIA_INFO_LEVELS = (0, 1, 2, 3, 9)
|
||||
FILE_FORMATS = ('csv', 'json', 'xml')
|
||||
FILE_FORMATS = ('csv', 'json', 'xml', 'm3u8')
|
||||
EXPORT_TYPES = ('all', 'collection', 'playlist')
|
||||
|
||||
def __init__(self, section_id=None, user_id=None, rating_key=None, file_format='csv',
|
||||
|
@ -101,7 +101,7 @@ class Export(object):
|
|||
self.section_id = helpers.cast_to_int(section_id) or None
|
||||
self.user_id = helpers.cast_to_int(user_id) or None
|
||||
self.rating_key = helpers.cast_to_int(rating_key) or None
|
||||
self.file_format = file_format
|
||||
self.file_format = str(file_format).lower()
|
||||
self.metadata_level = helpers.cast_to_int(metadata_level)
|
||||
self.media_info_level = helpers.cast_to_int(media_info_level)
|
||||
self.include_thumb = include_thumb
|
||||
|
@ -120,6 +120,14 @@ class Export(object):
|
|||
self.file_size = None
|
||||
self.success = False
|
||||
|
||||
# Reset export options for m3u8
|
||||
if self.file_format == 'm3u8':
|
||||
self.metadata_level = 1
|
||||
self.media_info_level = 1
|
||||
self.include_thumb = False
|
||||
self.include_art = False
|
||||
self.custom_fields = ''
|
||||
|
||||
def return_attrs(self, media_type, flatten=False):
|
||||
# o: current object
|
||||
# e: element in object attribute value list
|
||||
|
@ -1629,10 +1637,15 @@ class Export(object):
|
|||
outfile.write(json_data)
|
||||
|
||||
elif self.file_format == 'xml':
|
||||
xml_data = helpers.dict2xml({self.media_type: result}, root_node='export')
|
||||
xml_data = helpers.dict_to_xml({self.media_type: result}, root_node='export')
|
||||
with open(filepath, 'w', encoding='utf-8') as outfile:
|
||||
outfile.write(xml_data)
|
||||
|
||||
elif self.file_format == 'm3u8':
|
||||
m3u8_data = self.dict_to_m3u8(result)
|
||||
with open(filepath, 'w', encoding='utf-8') as outfile:
|
||||
outfile.write(m3u8_data)
|
||||
|
||||
self.file_size = os.path.getsize(filepath)
|
||||
|
||||
if os.path.exists(images_folder):
|
||||
|
@ -1773,6 +1786,41 @@ class Export(object):
|
|||
def is_media_info_attr(attr):
|
||||
return attr.startswith('media.') or attr == 'locations'
|
||||
|
||||
def dict_to_m3u8(self, data):
|
||||
items = self._get_m3u8_items(data)
|
||||
|
||||
m3u8 = '#EXTM3U\n'
|
||||
m3u8 += '# Playlist: {}\n\n'.format(self.filename)
|
||||
m3u8_item_template = '# ratingKey: {ratingKey}\n#EXTINF:{duration},{title}\n{location}\n'
|
||||
m3u8_items = []
|
||||
|
||||
for item in items:
|
||||
m3u8_items.append(m3u8_item_template.format(**item))
|
||||
|
||||
m3u8 = m3u8 + '\n'.join(m3u8_items)
|
||||
|
||||
return m3u8
|
||||
|
||||
def _get_m3u8_items(self, data):
|
||||
items = []
|
||||
|
||||
for d in data:
|
||||
if 'locations' in d:
|
||||
location = {
|
||||
'ratingKey': d['ratingKey'],
|
||||
'duration': d['duration'],
|
||||
'title': d['title'],
|
||||
'location': d['locations'][0]
|
||||
}
|
||||
items.append(location)
|
||||
|
||||
child_media_type = self.CHILD_MEDIA_TYPES[d['type']]
|
||||
if child_media_type:
|
||||
child_locations = self._get_m3u8_items(d[self.PLURAL_MEDIA_TYPES[child_media_type]])
|
||||
items.extend(child_locations)
|
||||
|
||||
return items
|
||||
|
||||
|
||||
def get_export(export_id):
|
||||
db = database.MonitorDatabase()
|
||||
|
|
|
@ -1385,7 +1385,7 @@ def escape_xml(value):
|
|||
|
||||
|
||||
# https://gist.github.com/reimund/5435343/
|
||||
def dict2xml(d, root_node=None, indent=4, level=0):
|
||||
def dict_to_xml(d, root_node=None, indent=4, level=0):
|
||||
wrap = not bool(root_node is None or isinstance(d, list))
|
||||
root = root_node or 'objects'
|
||||
root_singular = root[:-1] if root.endswith('s') and isinstance(d, list) else root
|
||||
|
@ -1395,9 +1395,9 @@ def dict2xml(d, root_node=None, indent=4, level=0):
|
|||
if isinstance(d, dict):
|
||||
for key, value in sorted(d.items()):
|
||||
if isinstance(value, dict):
|
||||
children.append(dict2xml(value, key, level=level + 1))
|
||||
children.append(dict_to_xml(value, key, level=level + 1))
|
||||
elif isinstance(value, list):
|
||||
children.append(dict2xml(value, key, level=level + 1))
|
||||
children.append(dict_to_xml(value, key, level=level + 1))
|
||||
else:
|
||||
xml = '{} {}="{}"'.format(xml, key, escape_xml(value))
|
||||
elif isinstance(d, list):
|
||||
|
@ -1405,7 +1405,7 @@ def dict2xml(d, root_node=None, indent=4, level=0):
|
|||
# Custom tag replacement for collections/playlists
|
||||
if isinstance(value, dict) and root in ('children', 'items'):
|
||||
root_singular = value.get('type', root_singular)
|
||||
children.append(dict2xml(value, root_singular, level=level))
|
||||
children.append(dict_to_xml(value, root_singular, level=level))
|
||||
else:
|
||||
children.append(escape_xml(d))
|
||||
|
||||
|
|
|
@ -6707,6 +6707,9 @@ class WebInterface(object):
|
|||
elif result['file_format'] == 'xml':
|
||||
return serve_file(filepath, name=result['filename'], content_type='application/xml;charset=UTF-8')
|
||||
|
||||
elif result['file_format'] == 'm3u8':
|
||||
return serve_file(filepath, name=result['filename'], content_type='text/plain;charset=UTF-8')
|
||||
|
||||
else:
|
||||
if result and result.get('complete') == 0:
|
||||
msg = 'Export is still being processed.'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue