3 Commits
Author SHA1 Message Date
Luke Pulverenti a08bbd0f31 3.2.1 2019-08-12 14:04:56 -04:00
Luke Pulverenti f7a7fced43 update recordings on schedule 2019-05-09 13:14:09 -04:00
Luke Pulverenti 46ea3e4c21 switch to DateTimeOffset 2018-09-07 15:22:39 -04:00
19 changed files with 898 additions and 1273 deletions
-199
View File
@@ -1,199 +0,0 @@
# With more recent updates Visual Studio 2017 supports EditorConfig files out of the box
# Visual Studio Code needs an extension: https://github.com/editorconfig/editorconfig-vscode
# For emacs, vim, np++ and other editors, see here: https://github.com/editorconfig
###############################
# Core EditorConfig Options #
###############################
root = true
# All files
[*]
indent_style = space
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
max_line_length = 9999
# YAML indentation
[*.{yml,yaml}]
indent_size = 2
# XML indentation
[*.{csproj,xml}]
indent_size = 2
###############################
# .NET Coding Conventions #
###############################
[*.{cs,vb}]
# Organize usings
dotnet_sort_system_directives_first = true
# this. preferences
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_event = false:silent
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_readonly_field = true:suggestion
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
###############################
# Naming Conventions #
###############################
# Style Definitions (From Roslyn)
# Non-private static fields are PascalCase
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
# Constants are PascalCase
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
dotnet_naming_symbols.constants.applicable_kinds = field, local
dotnet_naming_symbols.constants.required_modifiers = const
dotnet_naming_style.constant_style.capitalization = pascal_case
# Static fields are camelCase and start with s_
dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion
dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static
dotnet_naming_style.static_field_style.capitalization = camel_case
dotnet_naming_style.static_field_style.required_prefix = _
# Instance fields are camelCase and start with _
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
dotnet_naming_symbols.instance_fields.applicable_kinds = field
dotnet_naming_style.instance_field_style.capitalization = camel_case
dotnet_naming_style.instance_field_style.required_prefix = _
# Locals and parameters are camelCase
dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
dotnet_naming_style.camel_case_style.capitalization = camel_case
# Local functions are PascalCase
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_style.local_function_style.capitalization = pascal_case
# By default, name items with PascalCase
dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.all_members.applicable_kinds = *
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
###############################
# C# Coding Conventions #
###############################
[*.cs]
# var preferences
csharp_style_var_for_built_in_types = true:silent
csharp_style_var_when_type_is_apparent = true:silent
csharp_style_var_elsewhere = true:silent
# Expression-bodied members
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
# Null-checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
# Expression-level preferences
csharp_prefer_braces = true:silent
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
###############################
# C# Formatting Rules #
###############################
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left
# Space preferences
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_around_binary_operators = before_and_after
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping preferences
csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true
-2
View File
@@ -106,5 +106,3 @@ Generated_Code #added for RIA/Silverlight projects
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
.vs
-41
View File
@@ -1,41 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/Jellyfin.Plugin.Enigma2/Jellyfin.Plugin.Enigma2.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/Jellyfin.Plugin.Enigma2/Jellyfin.Plugin.Enigma2.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/Jellyfin.Plugin.Enigma2/Jellyfin.Plugin.Enigma2.csproj"
],
"problemMatcher": "$msCompile"
}
]
}
@@ -1,228 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Enigma2</title>
</head>
<body>
<div data-role="page" id="Enigma2ConfigurationPage" class="page type-interior pluginConfigurationPage Enigma2ConfigurationPage" data-require="emby-button,emby-checkbox,emby-input">
<div data-role="content">
<div class="content-primary">
<h1>Enigma2</h1>
<form id="Enigma2ConfigurationForm" class="Enigma2ConfigurationForm">
<div class="inputContainer">
<input is="emby-input" id="txtHostName" type="text" label="Enigma2 hostname or ip address:" />
<div class="fieldDescription">
The host name (address) or ip address of your receiver
</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtStreamingPort" type="text" label="Enigma2 streaming port:" />
<div class="fieldDescription">
The Streaming port of your receiver eg. 8001
</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtWebInterfacePort" type="text" label="Enigma2 Web Interface port:" />
<div class="fieldDescription">
The web interface port of your receiver eg. 8000
</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtWebInterfaceUsername" type="text" label="Enigma2 Web Interface username:" />
<div class="fieldDescription">
The web interface username of your receiver (optional)
</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtWebInterfacePassword" type="password" label="Enigma2 Web Interface password:" />
<div class="fieldDescription">
The web interface password of your receiver (optional)
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkUseLoginForStreams" />
<span>Use login for streaming</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Use web interface login for streaming
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkUseSecureHTTPS" />
<span>Use secure HTTP (HTTPS)</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Use HTTPS to connect to your receiver
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkUseSecureHTTPSForStreams" />
<span>Use secure HTTP (HTTPS) for streams</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Use HTTPS to connect to streaming port
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkOnlyOneBouquet" />
<span>Fetch Only one TV bouquet</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Limit channels to only those contained within the specified TV Bouquet below (optional)
</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtTVBouquet" type="text" label="Enigma2 TVBouquet:" />
<div class="fieldDescription">
The TV Bouquet to load channels for (optional)
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkZapToChannel" />
<span>Zap before Channel switch (single tuner)</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Set if only one tuner within receiver
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkFetchPiconsFromWebInterface" />
<span>Fetch picons from webinterface</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Set if you want to retrieve Picons from the web interface of the receiver
</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtPiconsPath" type="text" label="Picons Path:" />
<div class="fieldDescription">
The local location of your Picons eg. C:\Picons\ (optional)
</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtRecordingPath" type="text" label="Recording Path:" />
<div class="fieldDescription">
The location to store your recordings on your receiver eg. /hdd/movie/ (optional)
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkTranscodedStream" />
<span>Request Transcoded Stream</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Request Transcoded Stream (h264, 720p, 1Mpbs and mp4 as default)
</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtTranscodingPort" type="text" label="Enigma2 transcoding port:" />
<div class="fieldDescription">
The Transcoding port of your receiver eg. 8002
</div>
</div>
<label class="checkboxContainer">
<input is="emby-checkbox" type="checkbox" id="chkDebugLogging" />
<span>Enable Enigma2 debug logging</span>
</label>
<div>
<button is="emby-button" type="submit" class="raised button-submit block"><span>Save</span></button>
</div>
</form>
</div>
</div>
<script type="text/javascript">
var Enigma2ConfigurationPage = {
pluginUniqueId: "193f29f9-ea6c-4595-a6f6-55e79d7c590a",
loadConfiguration: function () {
Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(Enigma2ConfigurationPage.pluginUniqueId).then(function (config) {
document.getElementById('txtHostName').value = config.HostName || "";
document.getElementById('txtStreamingPort').value = config.StreamingPort || "";
document.getElementById('txtWebInterfacePort').value = config.WebInterfacePort || "";
document.getElementById('txtWebInterfaceUsername').value = config.WebInterfaceUsername || "";
document.getElementById('txtWebInterfacePassword').value = config.WebInterfacePassword || "";
document.getElementById('chkUseLoginForStreams').checked = config.UseLoginForStreams || false;
document.getElementById('chkUseSecureHTTPS').checked = config.UseSecureHTTPS || false;
document.getElementById('chkUseSecureHTTPSForStreams').checked = config.UseSecureHTTPSForStreams || false;
document.getElementById('chkOnlyOneBouquet').checked = config.OnlyOneBouquet || false;
document.getElementById('txtTVBouquet').value = config.TVBouquet || "";
document.getElementById('chkZapToChannel').checked = config.ZapToChannel || false;
document.getElementById('chkFetchPiconsFromWebInterface').checked = config.FetchPiconsFromWebInterface || false;
document.getElementById('txtPiconsPath').value = config.PiconsPath || "";
document.getElementById('txtRecordingPath').value = config.RecordingPath || "";
document.getElementById('chkTranscodedStream').checked = config.TranscodedStream || false;
document.getElementById('txtTranscodingPort').value = config.TranscodingPort || "";
document.getElementById('chkDebugLogging').checked = config.EnableDebugLogging || false;
Dashboard.hideLoadingMsg();
});
},
saveConfiguration: function () {
Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(Enigma2ConfigurationPage.pluginUniqueId).then(function (config) {
config.HostName = document.getElementById('txtHostName').value;
config.StreamingPort = document.getElementById('txtStreamingPort').value;
config.WebInterfacePort = document.getElementById('txtWebInterfacePort').value;
config.WebInterfaceUsername = document.getElementById('txtWebInterfaceUsername').value;
config.WebInterfacePassword = document.getElementById('txtWebInterfacePassword').value;
config.UseLoginForStreams = document.getElementById('chkUseLoginForStreams').checked;
config.UseSecureHTTPS = document.getElementById('chkUseSecureHTTPS').checked;
config.UseSecureHTTPSForStreams = document.getElementById('chkUseSecureHTTPSForStreams').checked;
config.OnlyOneBouquet = document.getElementById('chkOnlyOneBouquet').checked;
config.TVBouquet = document.getElementById('txtTVBouquet').value;
config.ZapToChannel = document.getElementById('chkZapToChannel').checked;
config.FetchPiconsFromWebInterface = document.getElementById('chkFetchPiconsFromWebInterface').checked;
config.PiconsPath = document.getElementById('txtPiconsPath').value;
config.RecordingPath = document.getElementById('txtRecordingPath').value;
config.TranscodedStream = document.getElementById('chkTranscodedStream').checked;
config.TranscodingPort = document.getElementById('txtTranscodingPort').value;
config.EnableDebugLogging = document.getElementById('chkDebugLogging').checked;
ApiClient.updatePluginConfiguration(Enigma2ConfigurationPage.pluginUniqueId, config).then(Dashboard.processPluginConfigurationUpdateResult);
});
}
}
document.getElementById('Enigma2ConfigurationPage').addEventListener('pageshow', function () {
Enigma2ConfigurationPage.loadConfiguration();
});
document.getElementById('Enigma2ConfigurationForm').addEventListener('submit', function (e) {
e.preventDefault();
Enigma2ConfigurationPage.saveConfiguration();
});
</script>
</div>
</body>
</html>
@@ -1,71 +0,0 @@
using System;
using MediaBrowser.Model.LiveTv;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Enigma2.Helpers
{
public static class ChannelHelper
{
public static ChannelType GetChannelType(string channelType)
{
var type = new ChannelType();
if (channelType == "0x1")
{
type = ChannelType.TV;
}
else if (channelType == "0xa")
{
type = ChannelType.Radio;
}
return type;
}
}
public static class UtilsHelper
{
public static void DebugInformation(ILogger<LiveTvService> logger, string message)
{
var config = Plugin.Instance.Configuration;
var enableDebugLogging = config.EnableDebugLogging;
if (enableDebugLogging)
{
logger.LogDebug(message);
}
}
}
public static class RecordingHelper
{
}
public static class ApiHelper
{
private static readonly DateTime UnixEpoch =
new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public static long GetCurrentUnixTimestampMillis()
{
return (long)(DateTime.UtcNow - UnixEpoch).TotalMilliseconds;
}
public static DateTime DateTimeFromUnixTimestampMillis(long millis)
{
return UnixEpoch.AddMilliseconds(millis);
}
public static long GetCurrentUnixTimestampSeconds(DateTime date)
{
return (long)(date - UnixEpoch).TotalSeconds;
}
public static DateTime DateTimeFromUnixTimestampSeconds(long seconds)
{
return UnixEpoch.AddSeconds(seconds);
}
}
}
@@ -1,35 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<AssemblyVersion>6.0.0.0</AssemblyVersion>
<FileVersion>6.0.0.0</FileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net6.0|AnyCPU'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0|AnyCPU'">
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<None Remove="Configuration\configPage.html" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Configuration\configPage.html" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Jellyfin.Common" Version="10.9.0" />
<PackageReference Include="Jellyfin.Naming" Version="10.9.0" />
<PackageReference Include="Jellyfin.Controller" Version="10.9.0" />
<PackageReference Include="Jellyfin.Model" Version="10.9.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
</ItemGroup>
</Project>
-57
View File
@@ -1,57 +0,0 @@
using System;
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
using Jellyfin.Plugin.Enigma2.Configuration;
namespace Jellyfin.Plugin.Enigma2
{
/// <summary>
/// Class Plugin
/// </summary>
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
{
public Plugin(
IApplicationPaths applicationPaths,
IXmlSerializer xmlSerializer
) : base(applicationPaths, xmlSerializer)
{
Instance = this;
}
/// <summary>
/// Gets the name of the plugin
/// </summary>
/// <value>The name.</value>
public override string Name => "Enigma2";
/// <summary>
/// Gets the description.
/// </summary>
/// <value>The description.</value>
public override string Description => "Provides live TV using Enigma2 PVR as a back-end.";
private Guid _id = new Guid("193f29f9-ea6c-4595-a6f6-55e79d7c590a");
public override Guid Id => _id;
/// <summary>
/// Gets the instance.
/// </summary>
/// <value>The instance.</value>
public static Plugin Instance { get; private set; }
public IEnumerable<PluginPageInfo> GetPages()
{
return new[]
{
new PluginPageInfo
{
Name = "enigma2",
EmbeddedResourcePath = "Jellyfin.Plugin.Enigma2.Configuration.configPage.html"
}
};
}
}
}
@@ -1,20 +0,0 @@
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Http;
using Jellyfin.Plugin.Enigma2.Helpers;
namespace Jellyfin.Plugin.Enigma2
{
public class ServiceRegistrator : IPluginServiceRegistrator
{
/// <inheritdoc />
public void RegisterServices(IServiceCollection serviceCollection, IServerApplicationHost applicationHost)
{
serviceCollection.AddSingleton<ILiveTvService, LiveTvService>();
serviceCollection.AddSingleton<IChannel, RecordingsChannel>();
}
}
}
-121
View File
@@ -1,121 +0,0 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.
@@ -1,14 +1,9 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32616.157
# Visual Studio 15
VisualStudioVersion = 15.0.26730.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Plugin.Enigma2", "Jellyfin.Plugin.Enigma2\Jellyfin.Plugin.Enigma2.csproj", "{52B1ABDF-09C1-450E-B808-BBA0A9C19848}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A968FB09-8732-407C-8748-6DB383EB5D91}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Plugins.VuPlus", "MediaBrowser.Plugins.VuPlus\MediaBrowser.Plugins.VuPlus.csproj", "{52B1ABDF-09C1-450E-B808-BBA0A9C19848}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -1,6 +1,7 @@
using System;
using MediaBrowser.Model.Plugins;
namespace Jellyfin.Plugin.Enigma2.Configuration
namespace MediaBrowser.Plugins.VuPlus.Configuration
{
/// <summary>
/// Class PluginConfiguration
@@ -12,34 +13,26 @@ namespace Jellyfin.Plugin.Enigma2.Configuration
public string WebInterfacePort { get; set; }
public string WebInterfaceUsername { get; set; }
public string WebInterfacePassword { get; set; }
public bool UseLoginForStreams { get; set; }
public bool UseSecureHTTPS { get; set; }
public bool UseSecureHTTPSForStreams { get; set; }
public bool OnlyOneBouquet { get; set; }
public Boolean UseSecureHTTPS { get; set; }
public Boolean OnlyOneBouquet { get; set; }
public string TVBouquet { get; set; }
public bool ZapToChannel { get; set; }
public bool FetchPiconsFromWebInterface { get; set; }
public Boolean ZapToChannel { get; set; }
public Boolean FetchPiconsFromWebInterface { get; set; }
public string PiconsPath { get; set; }
public string RecordingPath { get; set; }
public bool TranscodedStream { get; set; }
public string TranscodingPort { get; set; }
public bool EnableDebugLogging { get; set; }
public Boolean EnableDebugLogging { get; set; }
public PluginConfiguration()
{
HostName = "localhost";
HostName = "http://localhost";
StreamingPort = "8001";
WebInterfacePort = "8000";
WebInterfaceUsername = "";
WebInterfaceUsername= "";
WebInterfacePassword = "";
UseLoginForStreams = false;
UseSecureHTTPS = false;
UseSecureHTTPSForStreams = false;
OnlyOneBouquet = true;
TVBouquet = "Favourites (TV)";
ZapToChannel = false;
@@ -48,10 +41,8 @@ namespace Jellyfin.Plugin.Enigma2.Configuration
RecordingPath = "";
TranscodedStream = false;
TranscodingPort = "8002";
EnableDebugLogging = false;
}
}
}
@@ -0,0 +1,204 @@
<!DOCTYPE html>
<html>
<head>
<title>Vu+</title>
</head>
<body>
<div data-role="page" class="page type-interior pluginConfigurationPage VuPlusConfigurationPage" data-require="emby-button,emby-checkbox,emby-input">
<div data-role="content">
<div class="content-primary">
<h1>VuPlus</h1>
<form class="VuPlusConfigurationForm">
<div class="inputContainer">
<input is="emby-input" id="txtHostName" type="text" label="Vu+ hostname or ip address:" />
<div class="fieldDescription">
The host name (address) or ip address of your receiver
</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtStreamingPort" type="text" label="Vu+ streaming port:" />
<div class="fieldDescription">
The Streaming port of your receiver eg. 8001
</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtWebInterfacePort" type="text" label="Vu+ Web Interface port:" />
<div class="fieldDescription">
The web Interface port of your receiver eg. 8000
</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtWebInterfaceUsername" type="text" label="Vu+ Web Interface username:" />
<div class="fieldDescription">
The web Interface username of your receiver (optional)
</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtWebInterfacePassword" type="text" label="Vu+ Web Interface password:" />
<div class="fieldDescription">
The web Interface password of your receiver (optional)
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkUseSecureHTTPS" />
<span>Use secure HTTP (HTTPS)</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Use HTTPS to connect to your receiver
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkOnlyOneBouquet" />
<span>Fetch Only one TV bouquet</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Limit channels to only those contained within the specified TV Bouquet below (optional)
</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtTVBouquet" type="text" label="Vu+ TVBouquet:" />
<div class="fieldDescription">
The TV Bouquet to load channels for (optional)
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkZapToChannel" />
<span>Zap before Channel switch (single tuner)</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Set if only one tuner within receiver
</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="chkFetchPiconsFromWebInterface" />
<span>Fetch picons from webinterface</span>
</label>
<div class="fieldDescription checkboxFieldDescription">
Set if you want to retrieve Picons from the web interface of the receiver
</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtPiconsPath" type="text" label="Picons Path:" />
<div class="fieldDescription">
The local location of your Picons eg. C:\Picons\ (optional)
</div>
</div>
<div class="inputContainer">
<input is="emby-input" id="txtRecordingPath" type="text" label="Recording Path:" />
<div class="fieldDescription">
The location to store your recordings on your receiver eg. /hdd/movie/ (optional)
</div>
</div>
<label class="checkboxContainer">
<input is="emby-checkbox" type="checkbox" id="chkDebugLogging" />
<span>Enable VuPlus debug logging</span>
</label>
<div>
<button is="emby-button" type="submit" class="raised button-submit block"><span>Save</span></button>
</div>
</form>
</div>
</div>
<script type="text/javascript">
var VuPlusConfigurationPage = {
pluginUniqueId: "9564ac10-af23-39bc-449f-624f23cfa48f"
};
$('.VuPlusConfigurationPage').on('pageshow', function (event) {
Dashboard.showLoadingMsg();
var page = this;
ApiClient.getPluginConfiguration(VuPlusConfigurationPage.pluginUniqueId).then(function (config) {
$('#txtHostName', page).val(config.HostName || "");
$('#txtStreamingPort', page).val(config.StreamingPort || "");
$('#txtWebInterfacePort', page).val(config.WebInterfacePort || "");
$('#txtWebInterfaceUsername', page).val(config.WebInterfaceUsername || "");
$('#txtWebInterfacePassword', page).val(config.WebInterfacePassword || "");
page.querySelector('#chkUseSecureHTTPS').checked = config.UseSecureHTTPS || false;
page.querySelector('#chkOnlyOneBouquet').checked = config.OnlyOneBouquet || false;
page.querySelector('#txtTVBouquet', page).val(config.TVBouquet || "");
page.querySelector('#chkZapToChannel').checked = config.ZapToChannel || false;
page.querySelector('#chkFetchPiconsFromWebInterface').checked = config.FetchPiconsFromWebInterface || false;
$('#txtPiconsPath', page).val(config.PiconsPath || "");
$('#txtRecordingPath', page).val(config.RecordingPath || "");
page.querySelector('#chkDebugLogging').checked = config.EnableDebugLogging || false;
Dashboard.hideLoadingMsg();
});
});
$('.VuPlusConfigurationForm').on('submit', function (e) {
Dashboard.showLoadingMsg();
var form = this;
ApiClient.getPluginConfiguration(VuPlusConfigurationPage.pluginUniqueId).then(function (config) {
//reset TV bouquet reference if TV Bouquet changed
//if (config.TVBouquet != $('#txtTVBouquet', form).val()) {
// config.tvBouquetSRef = '';
//}
//reset TV bouquet reference if Only 1 Bouquet not set
//if (form.querySelector('#chkOnlyOneBouquet').checked) {
//}
//else
//{
// config.tvBouquetSRef = '';
//}
config.HostName = $('#txtHostName', form).val();
config.StreamingPort = $('#txtStreamingPort', form).val();
config.WebInterfacePort = $('#txtWebInterfacePort', form).val();
config.WebInterfaceUsername = $('#txtWebInterfaceUsername', form).val();
config.WebInterfacePassword = $('#txtWebInterfacePassword', form).val();
config.UseSecureHTTPS = form.querySelector('#chkUseSecureHTTPS').checked;
config.OnlyOneBouquet = form.querySelector('#chkOnlyOneBouquet').checked;
config.TVBouquet = $('#txtTVBouquet', form).val();
config.ZapToChannel = form.querySelector('#chkZapToChannel').checked;
config.FetchPiconsFromWebInterface = form.querySelector('#chkFetchPiconsFromWebInterface').checked;
config.PiconsPath = $('#txtPiconsPath', form).val();
config.RecordingPath = $('#txtRecordingPath', form).val();
config.EnableDebugLogging = form.querySelector('#chkDebugLogging').checked;
ApiClient.updatePluginConfiguration(VuPlusConfigurationPage.pluginUniqueId, config).then(Dashboard.processPluginConfigurationUpdateResult);
});
// Disable default form submission
return false;
});
</script>
</div>
</body>
</html>
@@ -0,0 +1,40 @@
using MediaBrowser.Model.LiveTv;
using System;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Plugins.VuPlus.Helpers
{
public static class ChannelHelper
{
public static ChannelType GetChannelType(string channelType)
{
ChannelType type = new ChannelType();
if (channelType == "0x1")
{
type = ChannelType.TV;
}
else if (channelType == "0xa")
{
type = ChannelType.Radio;
}
return type;
}
}
public static class UtilsHelper
{
public static void DebugInformation(ILogger logger, string message)
{
var config = Plugin.Instance.Configuration;
bool enableDebugLogging = config.EnableDebugLogging;
if (enableDebugLogging)
{
logger.Debug(message);
}
}
}
}

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;</TargetFrameworks>
<AssemblyVersion>3.2.1</AssemblyVersion>
<FileVersion>3.2.1</FileVersion>
</PropertyGroup>
<ItemGroup>
<None Remove="Configuration\configPage.html" />
<None Remove="Images\thumb.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Configuration\configPage.html" />
<EmbeddedResource Include="Images\thumb.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="mediabrowser.server.core" Version="4.1.0.26" />
<PackageReference Include="System.Memory" Version="4.5.2" />
</ItemGroup>
</Project>
+83
View File
@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Plugins.VuPlus.Configuration;
using System.IO;
using MediaBrowser.Model.Drawing;
namespace MediaBrowser.Plugins.VuPlus
{
/// <summary>
/// Class Plugin
/// </summary>
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages, IHasThumbImage
{
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer)
{
Instance = this;
}
/// <summary>
/// Gets the name of the plugin
/// </summary>
/// <value>The name.</value>
public override string Name
{
get { return "VuPlus"; }
}
/// <summary>
/// Gets the description.
/// </summary>
/// <value>The description.</value>
public override string Description
{
get
{
return "Provides live tv using Vu+ / Enigma2 pvr as a back-end.";
}
}
private Guid _id = new Guid("9564ac10-af23-39bc-449f-624f23cfa48f");
public override Guid Id
{
get { return _id; }
}
/// <summary>
/// Gets the instance.
/// </summary>
/// <value>The instance.</value>
public static Plugin Instance { get; private set; }
public IEnumerable<PluginPageInfo> GetPages()
{
return new[]
{
new PluginPageInfo
{
Name = "vuplus",
EmbeddedResourcePath = "MediaBrowser.Plugins.VuPlus.Configuration.configPage.html"
}
};
}
public Stream GetThumbImage()
{
var type = GetType();
return type.Assembly.GetManifestResourceStream(type.Namespace + ".Images.thumb.png");
}
public ImageFormat ThumbImageFormat
{
get
{
return ImageFormat.Png;
}
}
}
}
@@ -1,53 +1,110 @@
using System;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Channels;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
using MediaBrowser.Controller.LiveTv;
using System.Linq;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.Dto;
using System.Globalization;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.LiveTv;
namespace Jellyfin.Plugin.Enigma2
namespace MediaBrowser.Plugins.VuPlus
{
public class RecordingsChannel : IChannel, IHasCacheKey, ISupportsDelete, ISupportsLatestMedia, ISupportsMediaProbe, IHasFolderAttributes
public class RecordingsChannel : IChannel, IHasCacheKey, ISupportsDelete, ISupportsLatestMedia, ISupportsMediaProbe, IHasFolderAttributes, IHasChangeEvent
{
private readonly ILogger<LiveTvService> _logger;
public ILiveTvManager _liveTvManager;
public RecordingsChannel(ILoggerFactory loggerFactory)
public event EventHandler ContentChanged;
private Timer _updateTimer;
public void OnContentChanged()
{
_logger = loggerFactory.CreateLogger<LiveTvService>();
_logger.LogDebug("[Enigma2] RecordingsChannel()");
if (ContentChanged != null)
{
ContentChanged(this, EventArgs.Empty);
}
}
public string Name => "Enigma2 Recordings";
private void OnUpdateTimerCallback(object state)
{
OnContentChanged();
}
public string[] Attributes => new[] { "Recordings" };
public RecordingsChannel(ILiveTvManager liveTvManager)
{
_liveTvManager = liveTvManager;
public string Description => "Enigma2 Recordings";
var interval = TimeSpan.FromMinutes(15);
_updateTimer = new Timer(OnUpdateTimerCallback, null, interval, interval);
}
public string DataVersion => "1";
public void Dispose()
{
if (_updateTimer != null)
{
_updateTimer.Dispose();
_updateTimer = null;
}
}
public string HomePageUrl => null;
public string Name
{
get
{
return "VuPlus Recordings";
}
}
public ChannelParentalRating ParentalRating => ChannelParentalRating.GeneralAudience;
public string[] Attributes
{
get
{
return new[] { "Recordings" };
}
}
public string Description
{
get
{
return "VuPlus Recordings";
}
}
public string DataVersion
{
get
{
return "1";
}
}
public string HomePageUrl
{
get { return null; }
}
public ChannelParentalRating ParentalRating
{
get { return ChannelParentalRating.GeneralAudience; }
}
public string GetCacheKey(string userId)
{
var now = DateTime.UtcNow;
var now = DateTimeOffset.UtcNow;
var values = new List<string>
{
now.DayOfYear.ToString(CultureInfo.InvariantCulture),
now.Hour.ToString(CultureInfo.InvariantCulture)
};
var values = new List<string>();
values.Add(now.DayOfYear.ToString(CultureInfo.InvariantCulture));
values.Add(now.Hour.ToString(CultureInfo.InvariantCulture));
double minute = now.Minute;
minute /= 5;
@@ -65,8 +122,8 @@ namespace Jellyfin.Plugin.Enigma2
{
ContentTypes = new List<ChannelMediaContentType>
{
ChannelMediaContentType.Movie,
ChannelMediaContentType.Episode,
ChannelMediaContentType.Movie,
ChannelMediaContentType.Episode,
ChannelMediaContentType.Clip
},
MediaTypes = new List<ChannelMediaType>
@@ -84,7 +141,7 @@ namespace Jellyfin.Plugin.Enigma2
{
return Task.FromResult(new DynamicImageResponse
{
Path = "https://github.com/CompoUK/MediaBrowser.Plugins.VuPlus/blob/master/src/Images/Vu+.png?raw=true",
Path = "https://github.com/MediaBrowser/MediaBrowser.Plugins.VuPlus/raw/master/MediaBrowser.Plugins.VuPlus/Images/thumb.png",
Protocol = MediaProtocol.Http,
HasImage = true
});
@@ -111,7 +168,7 @@ namespace Jellyfin.Plugin.Enigma2
private LiveTvService GetService()
{
return LiveTvService.Instance;
return _liveTvManager.Services.OfType<LiveTvService>().First();
}
public bool CanDelete(BaseItem item)
@@ -124,13 +181,6 @@ namespace Jellyfin.Plugin.Enigma2
return GetService().DeleteRecordingAsync(id, cancellationToken);
}
public async Task<IEnumerable<ChannelItemInfo>> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken)
{
var result = await GetChannelItems(new InternalChannelItemQuery(), i => true, cancellationToken).ConfigureAwait(false);
return result.Items.OrderByDescending(i => i.DateCreated ?? DateTime.MinValue);
}
public Task<ChannelItemResult> GetChannelItems(InternalChannelItemQuery query, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(query.FolderId))
@@ -182,10 +232,14 @@ namespace Jellyfin.Plugin.Enigma2
var service = GetService();
var allRecordings = await service.GetAllRecordingsAsync(cancellationToken).ConfigureAwait(false);
return new ChannelItemResult()
var result = new ChannelItemResult()
{
Items = allRecordings.Where(filter).Select(ConvertToChannelItem).ToList()
Items = new List<ChannelItemInfo>()
};
result.Items.AddRange(allRecordings.Where(filter).Select(ConvertToChannelItem));
return result;
}
private ChannelItemInfo ConvertToChannelItem(MyRecordingInfo item)
@@ -204,7 +258,7 @@ namespace Jellyfin.Plugin.Enigma2
//HomePageUrl = item.HomePageUrl
Id = item.Id,
//IndexNumber = item.IndexNumber,
MediaType = item.ChannelType == ChannelType.TV ? ChannelMediaType.Video : ChannelMediaType.Audio,
MediaType = item.ChannelType == Model.LiveTv.ChannelType.TV ? ChannelMediaType.Video : ChannelMediaType.Audio,
MediaSources = new List<MediaSourceInfo>
{
new MediaSourceInfo
@@ -222,7 +276,7 @@ namespace Jellyfin.Plugin.Enigma2
DateModified = item.DateLastUpdated,
Overview = item.Overview,
//People = item.People
IsLiveStream = item.Status == RecordingStatus.InProgress,
IsLiveStream = item.Status == Model.LiveTv.RecordingStatus.InProgress,
Etag = item.Status.ToString()
};
@@ -234,14 +288,16 @@ namespace Jellyfin.Plugin.Enigma2
var service = GetService();
var allRecordings = await service.GetAllRecordingsAsync(cancellationToken).ConfigureAwait(false);
var result = new ChannelItemResult()
{
Items = new List<ChannelItemInfo>()
};
var series = allRecordings
.Where(i => i.IsSeries)
.ToLookup(i => i.Name, StringComparer.OrdinalIgnoreCase);
var items = new List<ChannelItemInfo>();
items.AddRange(series.OrderBy(i => i.Key).Select(i => new ChannelItemInfo
result.Items.AddRange(series.OrderBy(i => i.Key).Select(i => new ChannelItemInfo
{
Name = i.Key,
FolderType = ChannelFolderType.Container,
@@ -254,7 +310,7 @@ namespace Jellyfin.Plugin.Enigma2
if (kids != null)
{
items.Add(new ChannelItemInfo
result.Items.Add(new ChannelItemInfo
{
Name = "Kids",
FolderType = ChannelFolderType.Container,
@@ -267,7 +323,7 @@ namespace Jellyfin.Plugin.Enigma2
var movies = allRecordings.FirstOrDefault(i => i.IsMovie);
if (movies != null)
{
items.Add(new ChannelItemInfo
result.Items.Add(new ChannelItemInfo
{
Name = "Movies",
FolderType = ChannelFolderType.Container,
@@ -280,7 +336,7 @@ namespace Jellyfin.Plugin.Enigma2
var news = allRecordings.FirstOrDefault(i => i.IsNews);
if (news != null)
{
items.Add(new ChannelItemInfo
result.Items.Add(new ChannelItemInfo
{
Name = "News",
FolderType = ChannelFolderType.Container,
@@ -293,7 +349,7 @@ namespace Jellyfin.Plugin.Enigma2
var sports = allRecordings.FirstOrDefault(i => i.IsSports);
if (sports != null)
{
items.Add(new ChannelItemInfo
result.Items.Add(new ChannelItemInfo
{
Name = "Sports",
FolderType = ChannelFolderType.Container,
@@ -306,7 +362,7 @@ namespace Jellyfin.Plugin.Enigma2
var other = allRecordings.FirstOrDefault(i => !i.IsSports && !i.IsNews && !i.IsMovie && !i.IsKids && !i.IsSeries);
if (other != null)
{
items.Add(new ChannelItemInfo
result.Items.Add(new ChannelItemInfo
{
Name = "Others",
FolderType = ChannelFolderType.Container,
@@ -316,10 +372,7 @@ namespace Jellyfin.Plugin.Enigma2
});
}
return new ChannelItemResult()
{
Items = items
};
return result;
}
}
public class MyRecordingInfo
@@ -378,12 +431,12 @@ namespace Jellyfin.Plugin.Enigma2
/// <summary>
/// The start date of the recording, in UTC.
/// </summary>
public DateTime StartDate { get; set; }
public DateTimeOffset StartDate { get; set; }
/// <summary>
/// The end date of the recording, in UTC.
/// </summary>
public DateTime EndDate { get; set; }
public DateTimeOffset EndDate { get; set; }
/// <summary>
/// Gets or sets the program identifier.
@@ -430,7 +483,7 @@ namespace Jellyfin.Plugin.Enigma2
/// Gets or sets the original air date.
/// </summary>
/// <value>The original air date.</value>
public DateTime? OriginalAirDate { get; set; }
public DateTimeOffset? OriginalAirDate { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is movie.
@@ -513,7 +566,7 @@ namespace Jellyfin.Plugin.Enigma2
/// Gets or sets the date last updated.
/// </summary>
/// <value>The date last updated.</value>
public DateTime DateLastUpdated { get; set; }
public DateTimeOffset DateLastUpdated { get; set; }
public MyRecordingInfo()
{
-44
View File
@@ -1,44 +0,0 @@
---
# The name of the plugin; official plugins always start "jellyfin-plugin-"
name: "jellyfin-plugin-enigma2"
# The GUID of the plugin, found in Plugin.cs
guid: "193f29f9-ea6c-4595-a6f6-55e79d7c590a"
# The version of the plugin, starting at 1. Note that the AssemblyVersion
# and FileVersion flags in the `.csproj` file must have two additional
# fiels, e.g. 1.0.0 to be built correctly.
version: "5.0.1.0" # Please increment with each pull request
# The supported Jellyfin version, usually the earliest binary-compatible
# version. Based on the Jellyfin components from NuGet.
targetAbi: "10.8.0.0" # The earliest binary-compatible version
framework: "net6.0"
# The owner name of the plugin, "jellyfin" for official plugins. Change
# to your own name if you wish to remain a 3rd-party plugin.
owner: "knackebrot"
# A short description of the plugin
overview: "Enigma2"
# A longer and more detailed description of the plugin; use multiple
# lines as required for readability.
description: >
Live TV plugin for watching Enigma 2 channels and recordings
# The plugin category, in a general sense. These fields are dynamic.
category: "Live TV"
# A list of built artifacts to be bundled into the ZIP for installation.
# Include the main output file, as well as any dependency `.dll`s that
# might be required for the plugin to run.
artifacts:
- "Jellyfin.Plugin.Enigma2.dll"
# Build information values for the build infrastructure; these should
# not need to be changed usually.
build_type: "dotnet"
dotnet_configuration: "Release"
dotnet_framework: "net6.0"