filename templates added

This commit is contained in:
2024-03-03 23:05:32 +01:00
Unverified
parent 51302abffc
commit 06f40e52ad
16 changed files with 355 additions and 8 deletions

View File

@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="author" xml:space="preserve">
<value>{author} = author</value>
</data>
<data name="date" xml:space="preserve">
<value>{date:&lt;format&gt;} = publish date in specified format</value>
</data>
<data name="duration" xml:space="preserve">
<value>{duration:&lt;format&gt;} = duration in specified format</value>
</data>
<data name="id" xml:space="preserve">
<value>{id} = id (from url)</value>
</data>
<data name="source" xml:space="preserve">
<value>{source} = source</value>
</data>
<data name="title" xml:space="preserve">
<value>{title} = title</value>
</data>
<data name="views" xml:space="preserve">
<value>{views} = views</value>
</data>
</root>

View File

@@ -10,6 +10,7 @@ using System.Threading.Tasks;
using VDownload.Models;
using VDownload.Services.Data.Settings;
using VDownload.Services.UI.StoragePicker;
using VDownload.Services.Utility.Filename;
namespace VDownload.Core.ViewModels.Home.Helpers
{
@@ -19,6 +20,7 @@ namespace VDownload.Core.ViewModels.Home.Helpers
protected readonly ISettingsService _settingsService;
protected readonly IStoragePickerService _storagePickerService;
protected readonly IFilenameService _filenameService;
#endregion
@@ -80,10 +82,11 @@ namespace VDownload.Core.ViewModels.Home.Helpers
#region CONSTRUCTORS
public VideoViewModel(Video video, ISettingsService settingsService, IStoragePickerService storagePickerService)
public VideoViewModel(Video video, ISettingsService settingsService, IStoragePickerService storagePickerService, IFilenameService filenameService)
{
_settingsService = settingsService;
_storagePickerService = storagePickerService;
_filenameService = filenameService;
Video = video;
@@ -100,7 +103,7 @@ namespace VDownload.Core.ViewModels.Home.Helpers
TrimStart = TimeSpan.Zero;
TrimEnd = Duration;
DirectoryPath = _settingsService.Data.Common.Tasks.DefaultOutputDirectory;
Filename = Title.Length > 50 ? Title.Substring(0, 50) : Title;
Filename = _filenameService.CreateFilename(_settingsService.Data.Common.Tasks.FilenameTemplate, video);
VideoExtension = _settingsService.Data.Common.Tasks.DefaultVideoExtension;
AudioExtension = _settingsService.Data.Common.Tasks.DefaultAudioExtension;
}
@@ -120,7 +123,7 @@ namespace VDownload.Core.ViewModels.Home.Helpers
TrimStart = this.TrimStart,
TrimEnd = this.TrimEnd,
Directory = this.DirectoryPath,
Filename = string.Join("_", this.Filename.Split(Path.GetInvalidFileNameChars())),
Filename = _filenameService.SanitizeFilename(this.Filename),
Extension = (this.MediaType == MediaType.OnlyAudio ? this.AudioExtension.ToString() : this.VideoExtension.ToString()).ToLower(),
};
}

View File

@@ -16,6 +16,7 @@ using VDownload.Services.UI.StoragePicker;
using VDownload.Sources.Twitch.Configuration.Models;
using SimpleToolkit.MVVM;
using System.Text.RegularExpressions;
using VDownload.Services.Utility.Filename;
namespace VDownload.Core.ViewModels.Home
{
@@ -27,6 +28,7 @@ namespace VDownload.Core.ViewModels.Home
protected readonly ISettingsService _settingsService;
protected readonly IStoragePickerService _storagePickerService;
protected readonly IFilenameService _filenameService;
#endregion
@@ -172,11 +174,12 @@ namespace VDownload.Core.ViewModels.Home
#region CONSTRUCTORS
public HomePlaylistViewModel(IDownloadTaskManager tasksManager, ISettingsService settingsService, IStoragePickerService storagePickerService)
public HomePlaylistViewModel(IDownloadTaskManager tasksManager, ISettingsService settingsService, IStoragePickerService storagePickerService, IFilenameService filenameService)
{
_tasksManager = tasksManager;
_settingsService = settingsService;
_storagePickerService = storagePickerService;
_filenameService = filenameService;
_removedVideos = new List<VideoViewModel>();
@@ -221,7 +224,7 @@ namespace VDownload.Core.ViewModels.Home
Videos.Clear();
foreach (Video video in playlist)
{
Videos.Add(new VideoViewModel(video, _settingsService, _storagePickerService), true);
Videos.Add(new VideoViewModel(video, _settingsService, _storagePickerService, _filenameService), true);
}
UpdateFilter();
}

View File

@@ -11,6 +11,7 @@ using VDownload.Core.Tasks;
using VDownload.Models;
using VDownload.Services.Data.Settings;
using VDownload.Services.UI.StoragePicker;
using VDownload.Services.Utility.Filename;
namespace VDownload.Core.ViewModels.Home
{
@@ -22,6 +23,7 @@ namespace VDownload.Core.ViewModels.Home
protected readonly ISettingsService _settingsService;
protected readonly IStoragePickerService _storagePickerService;
protected readonly IFilenameService _filenameService;
#endregion
@@ -89,11 +91,12 @@ namespace VDownload.Core.ViewModels.Home
#region CONSTRUCTORS
public HomeVideoViewModel(IDownloadTaskManager tasksManager, ISettingsService settingsService, IStoragePickerService storagePickerService)
public HomeVideoViewModel(IDownloadTaskManager tasksManager, ISettingsService settingsService, IStoragePickerService storagePickerService, IFilenameService filenameService)
{
_tasksManager = tasksManager;
_settingsService = settingsService;
_storagePickerService = storagePickerService;
_filenameService = filenameService;
}
#endregion
@@ -121,7 +124,7 @@ namespace VDownload.Core.ViewModels.Home
TrimStart = TimeSpan.Zero;
TrimEnd = Duration;
DirectoryPath = _settingsService.Data.Common.Tasks.DefaultOutputDirectory;
Filename = Title.Length > 50 ? Title.Substring(0, 50) : Title;
Filename = _filenameService.CreateFilename(_settingsService.Data.Common.Tasks.FilenameTemplate, video);
VideoExtension = _settingsService.Data.Common.Tasks.DefaultVideoExtension;
AudioExtension = _settingsService.Data.Common.Tasks.DefaultAudioExtension;
}
@@ -167,7 +170,7 @@ namespace VDownload.Core.ViewModels.Home
TrimStart = this.TrimStart,
TrimEnd = this.TrimEnd,
Directory = this.DirectoryPath,
Filename = string.Join("_", this.Filename.Split(Path.GetInvalidFileNameChars())),
Filename = _filenameService.SanitizeFilename(this.Filename),
Extension = (this.MediaType == MediaType.OnlyAudio ? this.AudioExtension.ToString() : this.VideoExtension.ToString()).ToLower(),
};
}

View File

@@ -24,6 +24,7 @@
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.StoragePicker\VDownload.Services.UI.StoragePicker.csproj" />
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.StringResources\VDownload.Services.UI.StringResources.csproj" />
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.WebView\VDownload.Services.UI.WebView.csproj" />
<ProjectReference Include="..\..\VDownload.Services\VDownload.Services.Utility\VDownload.Services.Utility.Filename\VDownload.Services.Utility.Filename.csproj" />
<ProjectReference Include="..\..\VDownload.Sources\VDownload.Sources.Twitch\VDownload.Sources.Twitch.Authentication\VDownload.Sources.Twitch.Authentication.csproj" />
<ProjectReference Include="..\..\VDownload.Sources\VDownload.Sources\VDownload.Sources.csproj" />
<ProjectReference Include="..\VDownload.Core.Tasks\VDownload.Core.Tasks.csproj" />

View File

@@ -10,6 +10,7 @@ namespace VDownload.Models
{
#region PROPERTIES
public string Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public DateTime PublishDate { get; set; }

View File

@@ -6,6 +6,9 @@ namespace VDownload.Services.Data.Configuration
{
public class CommonConfiguration
{
[ConfigurationKeyName("filename_templates")]
public IEnumerable<FilenameTemplate> FilenameTemplates { get; set; }
[ConfigurationKeyName("path")]
public Models.Path Path { get; set; }

View File

@@ -0,0 +1,18 @@
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VDownload.Services.Data.Configuration.Models
{
public class FilenameTemplate
{
[ConfigurationKeyName("name")]
public string Name { get; set; }
[ConfigurationKeyName("wildcard")]
public string Wildcard { get; set; }
}
}

View File

@@ -19,6 +19,9 @@ namespace VDownload.Services.Data.Settings.Models
[JsonProperty("default_audio_extension")]
public AudioExtension DefaultAudioExtension { get; set; } = AudioExtension.MP3;
[JsonProperty("filename_template")]
public string FilenameTemplate { get; set; } = "{title}";
[JsonProperty("default_output_directory")]
public string DefaultOutputDirectory { get; set; } = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);

View File

@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using VDownload.Models;
using VDownload.Services.Data.Configuration;
using VDownload.Services.Data.Configuration.Models;
namespace VDownload.Services.Utility.Filename
{
public interface IFilenameService
{
string CreateFilename(string template, Video video);
string SanitizeFilename(string filename);
}
public class FilenameService : IFilenameService
{
#region SERVICES
protected readonly IConfigurationService _configurationService;
#endregion
#region CONSTRUCTORS
public FilenameService(IConfigurationService configurationService)
{
_configurationService = configurationService;
}
#endregion
#region PUBLIC METHODS
public string CreateFilename(string template, Video video)
{
string filename = template;
foreach (FilenameTemplate templateElement in _configurationService.Common.FilenameTemplates)
{
switch (templateElement.Name)
{
case "id": ReplaceInPlace(ref filename, templateElement.Wildcard, video.Id); break;
case "title": ReplaceInPlace(ref filename, templateElement.Wildcard, video.Title); break;
case "author": ReplaceInPlace(ref filename, templateElement.Wildcard, video.Author); break;
case "views": ReplaceInPlace(ref filename, templateElement.Wildcard, video.Views.ToString()); break;
case "source": ReplaceInPlace(ref filename, templateElement.Wildcard, video.Source.ToString()); break;
case "date":
{
Regex regex = new Regex(templateElement.Wildcard);
foreach (Match match in regex.Matches(filename))
{
ReplaceInPlace(ref filename, match.Value, video.PublishDate.ToString(match.Groups[1].Value));
}
break;
}
case "duration":
{
Regex regex = new Regex(templateElement.Wildcard);
foreach (Match match in regex.Matches(filename))
{
ReplaceInPlace(ref filename, match.Value, video.Duration.ToString(match.Groups[1].Value));
}
break;
}
default: throw new Exception("Invalid template");
}
}
filename = SanitizeFilename(filename);
return filename;
}
public string SanitizeFilename(string filename)
{
char[] invalidChars = System.IO.Path.GetInvalidFileNameChars();
foreach (char c in invalidChars)
{
filename = filename.Replace(c, '_');
}
return filename;
}
#endregion
#region PRIVATE METHODS
protected void ReplaceInPlace(ref string filename, string oldValue, string newValue) => filename = filename.Replace(oldValue, newValue);
#endregion
}
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\VDownload.Models\VDownload.Models.csproj" />
<ProjectReference Include="..\..\VDownload.Services.Data\VDownload.Services.Data.Configuration\VDownload.Services.Data.Configuration.csproj" />
</ItemGroup>
</Project>

View File

@@ -176,6 +176,7 @@ namespace VDownload.Sources.Twitch
}
TwitchVod vod = new TwitchVod
{
Id = data.Id,
Title = data.Title,
Description = data.Description,
Author = data.UserName,
@@ -201,6 +202,7 @@ namespace VDownload.Sources.Twitch
TwitchClip clip = new TwitchClip
{
Id = data.Id,
Title = data.Title,
Author = data.BroadcasterName,
Creator = data.CreatorName,

View File

@@ -69,6 +69,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VDownload.Core.Tasks", "VDo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VDownload.Services.Utility.FFmpeg", "VDownload.Services\VDownload.Services.Utility\VDownload.Services.Utility.FFmpeg\VDownload.Services.Utility.FFmpeg.csproj", "{A3166F8A-ECAD-4D4B-9BE3-96FEC799B27B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VDownload.Services.Utility.Filename", "VDownload.Services\VDownload.Services.Utility\VDownload.Services.Utility.Filename\VDownload.Services.Utility.Filename.csproj", "{4647EFB5-A206-4F47-976D-BAED11B52579}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -503,6 +505,22 @@ Global
{A3166F8A-ECAD-4D4B-9BE3-96FEC799B27B}.Release|x64.Build.0 = Release|Any CPU
{A3166F8A-ECAD-4D4B-9BE3-96FEC799B27B}.Release|x86.ActiveCfg = Release|Any CPU
{A3166F8A-ECAD-4D4B-9BE3-96FEC799B27B}.Release|x86.Build.0 = Release|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Debug|ARM64.ActiveCfg = Debug|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Debug|ARM64.Build.0 = Debug|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Debug|x64.ActiveCfg = Debug|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Debug|x64.Build.0 = Debug|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Debug|x86.ActiveCfg = Debug|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Debug|x86.Build.0 = Debug|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Release|Any CPU.Build.0 = Release|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Release|ARM64.ActiveCfg = Release|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Release|ARM64.Build.0 = Release|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Release|x64.ActiveCfg = Release|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Release|x64.Build.0 = Release|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Release|x86.ActiveCfg = Release|Any CPU
{4647EFB5-A206-4F47-976D-BAED11B52579}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -536,6 +554,7 @@ Global
{E470FCE2-DB0D-4771-8C9D-43D8AB85FB80} = {8539067C-9968-4AEB-928C-FEDC43989A79}
{3BE998A3-1126-4496-BF60-80D0CEA4D24F} = {8539067C-9968-4AEB-928C-FEDC43989A79}
{A3166F8A-ECAD-4D4B-9BE3-96FEC799B27B} = {1020167A-4101-496E-82CF-41B65769DD28}
{4647EFB5-A206-4F47-976D-BAED11B52579} = {1020167A-4101-496E-82CF-41B65769DD28}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9FD7B842-C3E2-4FD0-AD8A-C8E619280AB7}

View File

@@ -16,6 +16,7 @@ using VDownload.Core.Views.Home;
using VDownload.Core.Views.Settings;
using VDownload.Services.Data.Authentication;
using VDownload.Services.Data.Configuration;
using VDownload.Services.Data.Configuration.Models;
using VDownload.Services.Data.Settings;
using VDownload.Services.UI.Dialogs;
using VDownload.Services.UI.DictionaryResources;
@@ -25,6 +26,7 @@ using VDownload.Services.UI.StringResources;
using VDownload.Services.UI.WebView;
using VDownload.Services.Utility.Encryption;
using VDownload.Services.Utility.FFmpeg;
using VDownload.Services.Utility.Filename;
using VDownload.Services.Utility.HttpClient;
using VDownload.Sources;
using VDownload.Sources.Twitch;
@@ -119,6 +121,7 @@ namespace VDownload
services.AddSingleton<IEncryptionService, EncryptionService>();
services.AddSingleton<IHttpClientService, HttpClientService>();
services.AddSingleton<IFFmpegService, FFmpegService>();
services.AddSingleton<IFilenameService, FilenameService>();
}
protected void BuildSourcesServices(IServiceCollection services)

View File

@@ -181,6 +181,7 @@
<ProjectReference Include="..\VDownload.Services\VDownload.Services.UI\VDownload.Services.UI.WebView\VDownload.Services.UI.WebView.csproj" />
<ProjectReference Include="..\VDownload.Services\VDownload.Services.Utility\VDownload.Services.Utility.Encryption\VDownload.Services.Utility.Encryption.csproj" />
<ProjectReference Include="..\VDownload.Services\VDownload.Services.Utility\VDownload.Services.Utility.FFmpeg\VDownload.Services.Utility.FFmpeg.csproj" />
<ProjectReference Include="..\VDownload.Services\VDownload.Services.Utility\VDownload.Services.Utility.Filename\VDownload.Services.Utility.Filename.csproj" />
<ProjectReference Include="..\VDownload.Services\VDownload.Services.Utility\VDownload.Services.Utility.HttpClient\VDownload.Services.Utility.HttpClient.csproj" />
<ProjectReference Include="..\VDownload.Sources\VDownload.Sources.Twitch\VDownload.Sources.Twitch.Api\VDownload.Sources.Twitch.Api.csproj" />
<ProjectReference Include="..\VDownload.Sources\VDownload.Sources.Twitch\VDownload.Sources.Twitch.Authentication\VDownload.Sources.Twitch.Authentication.csproj" />

View File

@@ -1,5 +1,35 @@
{
"common": {
"filename_templates": [
{
"name": "id",
"wildcard": "{id}"
},
{
"name": "title",
"wildcard": "{title}"
},
{
"name": "author",
"wildcard": "{author}"
},
{
"name": "views",
"wildcard": "{views}"
},
{
"name": "source",
"wildcard": "{source}"
},
{
"name": "date",
"wildcard": "{date:(.+)}"
},
{
"name": "duration",
"wildcard": "{duration:(.+)}"
}
],
"processing": {
"muxers": [
{