Move podcast types to own file; more WIP on role (#17)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="PodcastTypes.fs" />
|
||||
<Compile Include="SupportTypes.fs" />
|
||||
<Compile Include="DataTypes.fs" />
|
||||
<Compile Include="ViewModels.fs" />
|
||||
|
||||
@@ -0,0 +1,531 @@
|
||||
namespace MyWebLog
|
||||
|
||||
/// <summary>Valid values for the iTunes explicit rating</summary>
|
||||
[<Struct>]
|
||||
type ExplicitRating =
|
||||
| Yes
|
||||
| No
|
||||
| Clean
|
||||
|
||||
/// <summary>Parse a string into an explicit rating</summary>
|
||||
/// <param name="rating">The string representation of the rating</param>
|
||||
/// <returns>The <c>ExplicitRating</c> parsed from the string</returns>
|
||||
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
|
||||
static member Parse rating =
|
||||
match rating with
|
||||
| "yes" -> Yes
|
||||
| "no" -> No
|
||||
| "clean" -> Clean
|
||||
| _ -> invalidArg (nameof rating) $"{rating} is not a valid explicit rating"
|
||||
|
||||
/// <inheritdoc />
|
||||
override this.ToString() =
|
||||
match this with Yes -> "yes" | No -> "no" | Clean -> "clean"
|
||||
|
||||
|
||||
/// <summary>A location (specified by Podcast Index)</summary>
|
||||
type Location = {
|
||||
/// <summary>The name of the location (free-form text)</summary>
|
||||
Name: string
|
||||
|
||||
/// <summary>A geographic coordinate string (RFC 5870)</summary>
|
||||
Geo: string
|
||||
|
||||
/// <summary>An OpenStreetMap query</summary>
|
||||
Osm: string option
|
||||
}
|
||||
|
||||
open NodaTime
|
||||
|
||||
/// <summary>A chapter in a podcast episode</summary>
|
||||
type Chapter = {
|
||||
/// <summary>The start time for the chapter</summary>
|
||||
StartTime: Duration
|
||||
|
||||
/// <summary>The title for this chapter</summary>
|
||||
Title: string option
|
||||
|
||||
/// <summary>A URL for an image for this chapter</summary>
|
||||
ImageUrl: string option
|
||||
|
||||
/// <summary>A URL with information pertaining to this chapter</summary>
|
||||
Url: string option
|
||||
|
||||
/// <summary>Whether this chapter is hidden</summary>
|
||||
IsHidden: bool option
|
||||
|
||||
/// <summary>The episode end time for the chapter</summary>
|
||||
EndTime: Duration option
|
||||
|
||||
/// <summary>A location that applies to a chapter</summary>
|
||||
Location: Location option
|
||||
} with
|
||||
|
||||
/// <summary>An empty chapter</summary>
|
||||
static member Empty =
|
||||
{ StartTime = Duration.Zero
|
||||
Title = None
|
||||
ImageUrl = None
|
||||
Url = None
|
||||
IsHidden = None
|
||||
EndTime = None
|
||||
Location = None }
|
||||
|
||||
/// <summary>A group to which a podcast person can belong</summary>
|
||||
[<Struct>]
|
||||
type PersonGroup =
|
||||
| Administration
|
||||
| AudioPostProduction
|
||||
| AudioProduction
|
||||
| Cast
|
||||
| Community
|
||||
| CreativeDirection
|
||||
| Misc
|
||||
| VideoPostProduction
|
||||
| VideoProduction
|
||||
| Visuals
|
||||
| Writing
|
||||
|
||||
/// <summary>Parse a string into a group</summary>
|
||||
/// <param name="group">The string representation of the group</param>
|
||||
/// <returns>The <c>PersonGroup</c> instance parsed from the string</returns>
|
||||
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
|
||||
static member Parse group =
|
||||
match group with
|
||||
| "Administration" -> Administration
|
||||
| "Audio Post-Production" -> AudioPostProduction
|
||||
| "Audio Production" -> AudioProduction
|
||||
| "Cast" -> Cast
|
||||
| "Community" -> Community
|
||||
| "Creative Direction" -> CreativeDirection
|
||||
| "Misc." -> Misc
|
||||
| "Video Post-Production" -> VideoPostProduction
|
||||
| "Video Production" -> VideoProduction
|
||||
| "Visuals" -> Visuals
|
||||
| "Writing" -> Writing
|
||||
| _ -> invalidArg (nameof group) $"{group} is not a valid group"
|
||||
|
||||
/// <inheritdoc />
|
||||
override this.ToString() =
|
||||
match this with
|
||||
| Administration -> "Administration"
|
||||
| AudioPostProduction -> "Audio Post-Production"
|
||||
| AudioProduction -> "Audio Production"
|
||||
| Cast -> "Cast"
|
||||
| Community -> "Community"
|
||||
| CreativeDirection -> "Creative Direction"
|
||||
| Misc -> "Misc."
|
||||
| VideoPostProduction -> "Video Post-Production"
|
||||
| VideoProduction -> "Video Production"
|
||||
| Visuals -> "Visuals"
|
||||
| Writing -> "Writing"
|
||||
|
||||
|
||||
/// <summary>The role a person plays for a podcast or a podcast episode</summary>
|
||||
type PersonRole =
|
||||
| Announcer
|
||||
| AssistantCamera
|
||||
| AssistantDirector
|
||||
| AssistantEditor
|
||||
| AssociateProducer
|
||||
| AudioEditor
|
||||
| AudioEngineer
|
||||
| Author
|
||||
| BookingCoordinator
|
||||
| CameraGrip
|
||||
| CameraOperator
|
||||
| CoHost
|
||||
| Composer
|
||||
| Consultant
|
||||
| ContentManager
|
||||
| CoverArtDesigner
|
||||
| CoWriter
|
||||
| CreativeDirector
|
||||
| DevelopmentProducer
|
||||
| Director
|
||||
| EditorialDirector
|
||||
| ExecutiveProducer
|
||||
| FactChecker
|
||||
| FoleyArtist
|
||||
| GraphicDesigner
|
||||
| Guest
|
||||
| GuestHost
|
||||
| GuestWriter
|
||||
| Host
|
||||
| Intern
|
||||
| LightingDesigner
|
||||
| Logger
|
||||
| ManagingEditor
|
||||
| MarketingManager
|
||||
| MusicContributor
|
||||
| MusicProduction
|
||||
| Narrator
|
||||
| PostProductionEngineer
|
||||
| Producer
|
||||
| ProductionAssistant
|
||||
| ProductionCoordinator
|
||||
| RemoteRecordingEngineer
|
||||
| Reporter
|
||||
| Researcher
|
||||
| SalesManager
|
||||
| SalesRepresentative
|
||||
| ScriptCoordinator
|
||||
| ScriptEditor
|
||||
| SeniorProducer
|
||||
| SocialMediaManager
|
||||
| Songwriter
|
||||
| SoundDesigner
|
||||
| StoryEditor
|
||||
| StudioCoordinator
|
||||
| TechnicalDirector
|
||||
| TechnicalManager
|
||||
| ThemeMusic
|
||||
| Transcriber
|
||||
| Translator
|
||||
| VideoEditor
|
||||
| VoiceActor
|
||||
| Writer
|
||||
| WritingEditor
|
||||
|
||||
/// <summary>Parse a string into a role</summary>
|
||||
/// <param name="role">The string representation of the role</param>
|
||||
/// <returns>The <c>PersonRole</c> instance parsed from the string</returns>
|
||||
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
|
||||
static member Parse role =
|
||||
match role with
|
||||
| "Announcer" -> Announcer
|
||||
| "Assistant Camera" -> AssistantCamera
|
||||
| "Assistant Director" -> AssistantDirector
|
||||
| "Assistant Editor" -> AssistantEditor
|
||||
| "Associate Producer" -> AssociateProducer
|
||||
| "Audio Editor" -> AudioEditor
|
||||
| "Audio Engineer" -> AudioEngineer
|
||||
| "Author" -> Author
|
||||
| "Booking Coordinator" -> BookingCoordinator
|
||||
| "Camera Grip" -> CameraGrip
|
||||
| "Camera Operator" -> CameraOperator
|
||||
| "Co-Host" -> CoHost
|
||||
| "Composer" -> Composer
|
||||
| "Consultant" -> Consultant
|
||||
| "Content Manager" -> ContentManager
|
||||
| "Cover Art Designer" -> CoverArtDesigner
|
||||
| "Co-Writer" -> CoWriter
|
||||
| "Creative Director" -> CreativeDirector
|
||||
| "Development Producer" -> DevelopmentProducer
|
||||
| "Director" -> Director
|
||||
| "Editorial Director" -> EditorialDirector
|
||||
| "Executive Producer" -> ExecutiveProducer
|
||||
| "Fact Checker" -> FactChecker
|
||||
| "Foley Artist" -> FoleyArtist
|
||||
| "Graphic Designer" -> GraphicDesigner
|
||||
| "Guest" -> Guest
|
||||
| "Guest Host" -> GuestHost
|
||||
| "Guest Writer" -> GuestWriter
|
||||
| "Host" -> Host
|
||||
| "Intern" -> Intern
|
||||
| "Lighting Designer" -> LightingDesigner
|
||||
| "Logger" -> Logger
|
||||
| "Managing Editor" -> ManagingEditor
|
||||
| "Marketing Manager" -> MarketingManager
|
||||
| "Music Contributor" -> MusicContributor
|
||||
| "Music Production" -> MusicProduction
|
||||
| "Narrator" -> Narrator
|
||||
| "Post Production Engineer" -> PostProductionEngineer
|
||||
| "Producer" -> Producer
|
||||
| "Production Assistant" -> ProductionAssistant
|
||||
| "Production Coordinator" -> ProductionCoordinator
|
||||
| "Remote Recording Engineer" -> RemoteRecordingEngineer
|
||||
| "Reporter" -> Reporter
|
||||
| "Researcher" -> Researcher
|
||||
| "Sales Manager" -> SalesManager
|
||||
| "Sales Representative" -> SalesRepresentative
|
||||
| "Script Coordinator" -> ScriptCoordinator
|
||||
| "Script Editor" -> ScriptEditor
|
||||
| "Senior Producer" -> SeniorProducer
|
||||
| "Social Media Manager" -> SocialMediaManager
|
||||
| "Songwriter" -> Songwriter
|
||||
| "Sound Designer" -> SoundDesigner
|
||||
| "Story Editor" -> StoryEditor
|
||||
| "Studio Coordinator" -> StudioCoordinator
|
||||
| "Technical Director" -> TechnicalDirector
|
||||
| "Technical Manager" -> TechnicalManager
|
||||
| "Theme Music" -> ThemeMusic
|
||||
| "Transcriber" -> Transcriber
|
||||
| "Translator" -> Translator
|
||||
| "Editor (Video)" -> VideoEditor
|
||||
| "Voice Actor" -> VoiceActor
|
||||
| "Writer" -> Writer
|
||||
| "Editor (Writing)" -> WritingEditor
|
||||
| _ -> invalidArg (nameof role) $"{role} is not a valid role"
|
||||
|
||||
/// <summary>Role / group cross-reference</summary>
|
||||
static member GroupXref =
|
||||
[ Announcer, Cast
|
||||
AssistantCamera, VideoProduction
|
||||
AssistantDirector, CreativeDirection
|
||||
AssistantEditor, VideoPostProduction
|
||||
AssociateProducer, CreativeDirection
|
||||
AudioEditor, AudioPostProduction
|
||||
AudioEngineer, AudioProduction
|
||||
Author, Writing
|
||||
BookingCoordinator, Administration
|
||||
CameraGrip, VideoProduction
|
||||
CameraOperator, VideoProduction
|
||||
CoHost, Cast
|
||||
Composer, AudioPostProduction
|
||||
ContentManager, Administration
|
||||
Consultant, Misc
|
||||
CoverArtDesigner, Visuals
|
||||
CoWriter, Writing
|
||||
CreativeDirector, CreativeDirection
|
||||
DevelopmentProducer, CreativeDirection
|
||||
Director, CreativeDirection
|
||||
EditorialDirector, Writing
|
||||
ExecutiveProducer, CreativeDirection
|
||||
FactChecker, Writing
|
||||
FoleyArtist, AudioPostProduction
|
||||
GraphicDesigner, Visuals
|
||||
Guest, Cast
|
||||
GuestHost, Cast
|
||||
GuestWriter, Writing
|
||||
Host, Cast
|
||||
Intern, Misc
|
||||
LightingDesigner, VideoProduction
|
||||
Logger, Writing
|
||||
ManagingEditor, Writing
|
||||
MarketingManager, Administration
|
||||
MusicContributor, AudioPostProduction
|
||||
MusicProduction, AudioPostProduction
|
||||
Narrator, Cast
|
||||
PostProductionEngineer, AudioProduction
|
||||
Producer, CreativeDirection
|
||||
ProductionAssistant, Administration
|
||||
ProductionCoordinator, Administration
|
||||
RemoteRecordingEngineer, AudioProduction
|
||||
Reporter, Cast
|
||||
Researcher, Writing
|
||||
SalesManager, Administration
|
||||
SalesRepresentative, Administration
|
||||
ScriptCoordinator, Writing
|
||||
ScriptEditor, Writing
|
||||
SeniorProducer, CreativeDirection
|
||||
SocialMediaManager, Community
|
||||
Songwriter, Writing
|
||||
SoundDesigner, AudioPostProduction
|
||||
StoryEditor, Writing
|
||||
StudioCoordinator, AudioProduction
|
||||
TechnicalDirector, AudioProduction
|
||||
TechnicalManager, AudioProduction
|
||||
ThemeMusic, AudioPostProduction
|
||||
Transcriber, Writing
|
||||
Translator, Writing
|
||||
VideoEditor, VideoPostProduction
|
||||
VoiceActor, Cast
|
||||
Writer, Writing
|
||||
WritingEditor, Writing ]
|
||||
|
||||
/// <inheritdoc />
|
||||
override this.ToString() =
|
||||
match this with
|
||||
| Announcer -> "Announcer"
|
||||
| AssistantCamera -> "Assistant Camera"
|
||||
| AssistantDirector -> "Assistant Director"
|
||||
| AssistantEditor -> "Assistant Editor"
|
||||
| AssociateProducer -> "Associate Producer"
|
||||
| AudioEditor -> "Audio Editor"
|
||||
| AudioEngineer -> "Audio Engineer"
|
||||
| Author -> "Author"
|
||||
| BookingCoordinator -> "Booking Coordinator"
|
||||
| CameraGrip -> "Camera Grip"
|
||||
| CameraOperator -> "Camera Operator"
|
||||
| CoHost -> "Co-Host"
|
||||
| Composer -> "Composer"
|
||||
| Consultant -> "Consultant"
|
||||
| ContentManager -> "Content Manager"
|
||||
| CoverArtDesigner -> "Cover Art Designer"
|
||||
| CoWriter -> "Co-Writer"
|
||||
| CreativeDirector -> "Creative Director"
|
||||
| DevelopmentProducer -> "Development Producer"
|
||||
| Director -> "Director"
|
||||
| EditorialDirector -> "Editorial Director"
|
||||
| ExecutiveProducer -> "Executive Producer"
|
||||
| FactChecker -> "Fact Checker"
|
||||
| FoleyArtist -> "Foley Artist"
|
||||
| GraphicDesigner -> "Graphic Designer"
|
||||
| Guest -> "Guest"
|
||||
| GuestHost -> "Guest Host"
|
||||
| GuestWriter -> "Guest Writer"
|
||||
| Host -> "Host"
|
||||
| Intern -> "Intern"
|
||||
| LightingDesigner -> "Lighting Designer"
|
||||
| Logger -> "Logger"
|
||||
| ManagingEditor -> "Managing Editor"
|
||||
| MarketingManager -> "Marketing Manager"
|
||||
| MusicContributor -> "Music Contributor"
|
||||
| MusicProduction -> "Music Production"
|
||||
| Narrator -> "Narrator"
|
||||
| PostProductionEngineer -> "Post Production Engineer"
|
||||
| Producer -> "Producer"
|
||||
| ProductionAssistant -> "Production Assistant"
|
||||
| ProductionCoordinator -> "Production Coordinator"
|
||||
| RemoteRecordingEngineer -> "Remote Recording Engineer"
|
||||
| Reporter -> "Reporter"
|
||||
| Researcher -> "Researcher"
|
||||
| SalesManager -> "Sales Manager"
|
||||
| SalesRepresentative -> "Sales Representative"
|
||||
| ScriptCoordinator -> "Script Coordinator"
|
||||
| ScriptEditor -> "Script Editor"
|
||||
| SeniorProducer -> "Senior Producer"
|
||||
| SocialMediaManager -> "Social Media Manager"
|
||||
| Songwriter -> "Songwriter"
|
||||
| SoundDesigner -> "Sound Designer"
|
||||
| StoryEditor -> "Story Editor"
|
||||
| StudioCoordinator -> "Studio Coordinator"
|
||||
| TechnicalDirector -> "Technical Director"
|
||||
| TechnicalManager -> "Technical Manager"
|
||||
| ThemeMusic -> "Theme Music"
|
||||
| Transcriber -> "Transcriber"
|
||||
| Translator -> "Translator"
|
||||
| VideoEditor -> "Editor (Video)"
|
||||
| VoiceActor -> "Voice Actor"
|
||||
| Writer -> "Writer"
|
||||
| WritingEditor -> "Editor (Writing)"
|
||||
|
||||
/// <summary>Get the display name of the role</summary>
|
||||
/// <remarks>Both Video Production and Writing have an "Editor"; this is how it should be rendered in RSS feeds and
|
||||
/// in the UI</remarks>
|
||||
member this.ToDisplay() =
|
||||
match this with VideoEditor | WritingEditor -> "Editor" | _ -> string this
|
||||
|
||||
/// <summary>Get the <c>PersonGroup</c> to which this role belongs</summary>
|
||||
member this.Group =
|
||||
PersonRole.GroupXref |> List.find (fun it -> fst it = this) |> snd
|
||||
|
||||
|
||||
open NodaTime.Text
|
||||
|
||||
/// <summary>A podcast episode</summary>
|
||||
type Episode = {
|
||||
/// <summary>The URL to the media file for the episode (may be permalink)</summary>
|
||||
Media: string
|
||||
|
||||
/// <summary>The length of the media file, in bytes</summary>
|
||||
Length: int64
|
||||
|
||||
/// <summary>The duration of the episode</summary>
|
||||
Duration: Duration option
|
||||
|
||||
/// <summary>The media type of the file (overrides podcast default if present)</summary>
|
||||
MediaType: string option
|
||||
|
||||
/// <summary>The URL to the image file for this episode (overrides podcast image if present, may be permalink)</summary>
|
||||
ImageUrl: string option
|
||||
|
||||
/// <summary>A subtitle for this episode</summary>
|
||||
Subtitle: string option
|
||||
|
||||
/// <summary>This episode's explicit rating (overrides podcast rating if present)</summary>
|
||||
Explicit: ExplicitRating option
|
||||
|
||||
/// <summary>Chapters for this episode</summary>
|
||||
Chapters: Chapter list option
|
||||
|
||||
/// <summary>A link to a chapter file</summary>
|
||||
ChapterFile: string option
|
||||
|
||||
/// <summary>The MIME type for the chapter file</summary>
|
||||
ChapterType: string option
|
||||
|
||||
/// <summary>Whether the chapters have locations that should be displayed as waypoints</summary>
|
||||
ChapterWaypoints: bool option
|
||||
|
||||
/// <summary>The URL for the transcript of the episode (may be permalink)</summary>
|
||||
TranscriptUrl: string option
|
||||
|
||||
/// <summary>The MIME type of the transcript</summary>
|
||||
TranscriptType: string option
|
||||
|
||||
/// <summary>The language in which the transcript is written</summary>
|
||||
TranscriptLang: string option
|
||||
|
||||
/// <summary>If true, the transcript will be declared (in the feed) to be a captions file</summary>
|
||||
TranscriptCaptions: bool option
|
||||
|
||||
/// <summary>The season number (for serialized podcasts)</summary>
|
||||
SeasonNumber: int option
|
||||
|
||||
/// <summary>A description of the season</summary>
|
||||
SeasonDescription: string option
|
||||
|
||||
/// <summary>The episode number</summary>
|
||||
EpisodeNumber: double option
|
||||
|
||||
/// <summary>A description of the episode</summary>
|
||||
EpisodeDescription: string option
|
||||
} with
|
||||
|
||||
/// <summary>An empty episode</summary>
|
||||
static member Empty =
|
||||
{ Media = ""
|
||||
Length = 0L
|
||||
Duration = None
|
||||
MediaType = None
|
||||
ImageUrl = None
|
||||
Subtitle = None
|
||||
Explicit = None
|
||||
Chapters = None
|
||||
ChapterFile = None
|
||||
ChapterType = None
|
||||
ChapterWaypoints = None
|
||||
TranscriptUrl = None
|
||||
TranscriptType = None
|
||||
TranscriptLang = None
|
||||
TranscriptCaptions = None
|
||||
SeasonNumber = None
|
||||
SeasonDescription = None
|
||||
EpisodeNumber = None
|
||||
EpisodeDescription = None }
|
||||
|
||||
/// <summary>Format a duration for an episode</summary>
|
||||
/// <returns>A duration formatted in hours, minutes, and seconds</returns>
|
||||
member this.FormatDuration() =
|
||||
this.Duration |> Option.map (DurationPattern.CreateWithInvariantCulture("H:mm:ss").Format)
|
||||
|
||||
|
||||
/// <summary>PodcastIndex.org podcast:medium allowed values</summary>
|
||||
[<Struct>]
|
||||
type PodcastMedium =
|
||||
| Podcast
|
||||
| Music
|
||||
| Video
|
||||
| Film
|
||||
| Audiobook
|
||||
| Newsletter
|
||||
| Blog
|
||||
|
||||
/// <summary>Parse a string into a podcast medium</summary>
|
||||
/// <param name="medium">The string to be parsed</param>
|
||||
/// <returns>The <c>PodcastMedium</c> parsed from the string</returns>
|
||||
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
|
||||
static member Parse medium =
|
||||
match medium with
|
||||
| "podcast" -> Podcast
|
||||
| "music" -> Music
|
||||
| "video" -> Video
|
||||
| "film" -> Film
|
||||
| "audiobook" -> Audiobook
|
||||
| "newsletter" -> Newsletter
|
||||
| "blog" -> Blog
|
||||
| _ -> invalidArg (nameof medium) $"{medium} is not a valid podcast medium"
|
||||
|
||||
/// <inheritdoc />
|
||||
override this.ToString() =
|
||||
match this with
|
||||
| Podcast -> "podcast"
|
||||
| Music -> "music"
|
||||
| Video -> "video"
|
||||
| Film -> "film"
|
||||
| Audiobook -> "audiobook"
|
||||
| Newsletter -> "newsletter"
|
||||
| Blog -> "blog"
|
||||
|
||||
@@ -203,492 +203,6 @@ type CommentStatus =
|
||||
match this with Approved -> "Approved" | Pending -> "Pending" | Spam -> "Spam"
|
||||
|
||||
|
||||
/// <summary>Valid values for the iTunes explicit rating</summary>
|
||||
[<Struct>]
|
||||
type ExplicitRating =
|
||||
| Yes
|
||||
| No
|
||||
| Clean
|
||||
|
||||
/// <summary>Parse a string into an explicit rating</summary>
|
||||
/// <param name="rating">The string representation of the rating</param>
|
||||
/// <returns>The <c>ExplicitRating</c> parsed from the string</returns>
|
||||
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
|
||||
static member Parse rating =
|
||||
match rating with
|
||||
| "yes" -> Yes
|
||||
| "no" -> No
|
||||
| "clean" -> Clean
|
||||
| _ -> invalidArg (nameof rating) $"{rating} is not a valid explicit rating"
|
||||
|
||||
/// <inheritdoc />
|
||||
override this.ToString() =
|
||||
match this with Yes -> "yes" | No -> "no" | Clean -> "clean"
|
||||
|
||||
|
||||
/// <summary>A location (specified by Podcast Index)</summary>
|
||||
type Location = {
|
||||
/// <summary>The name of the location (free-form text)</summary>
|
||||
Name: string
|
||||
|
||||
/// <summary>A geographic coordinate string (RFC 5870)</summary>
|
||||
Geo: string
|
||||
|
||||
/// <summary>An OpenStreetMap query</summary>
|
||||
Osm: string option
|
||||
}
|
||||
|
||||
|
||||
/// <summary>A chapter in a podcast episode</summary>
|
||||
type Chapter = {
|
||||
/// <summary>The start time for the chapter</summary>
|
||||
StartTime: Duration
|
||||
|
||||
/// <summary>The title for this chapter</summary>
|
||||
Title: string option
|
||||
|
||||
/// <summary>A URL for an image for this chapter</summary>
|
||||
ImageUrl: string option
|
||||
|
||||
/// <summary>A URL with information pertaining to this chapter</summary>
|
||||
Url: string option
|
||||
|
||||
/// <summary>Whether this chapter is hidden</summary>
|
||||
IsHidden: bool option
|
||||
|
||||
/// <summary>The episode end time for the chapter</summary>
|
||||
EndTime: Duration option
|
||||
|
||||
/// <summary>A location that applies to a chapter</summary>
|
||||
Location: Location option
|
||||
} with
|
||||
|
||||
/// <summary>An empty chapter</summary>
|
||||
static member Empty =
|
||||
{ StartTime = Duration.Zero
|
||||
Title = None
|
||||
ImageUrl = None
|
||||
Url = None
|
||||
IsHidden = None
|
||||
EndTime = None
|
||||
Location = None }
|
||||
|
||||
/// <summary>A group to which a podcast person can belong</summary>
|
||||
type PersonGroup =
|
||||
| Administration
|
||||
| AudioPostProduction
|
||||
| AudioProduction
|
||||
| Cast
|
||||
| Community
|
||||
| CreativeDirection
|
||||
| Misc
|
||||
| VideoPostProduction
|
||||
| VideoProduction
|
||||
| Visuals
|
||||
| Writing
|
||||
|
||||
/// <summary>Parse a string into a group</summary>
|
||||
/// <param name="group">The string representation of the group</param>
|
||||
/// <returns>The <c>PersonGroup</c> instance parsed from the string</returns>
|
||||
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
|
||||
static member Parse group =
|
||||
match group with
|
||||
| "Administration" -> Administration
|
||||
| "Audio Post-Production" -> AudioPostProduction
|
||||
| "Audio Production" -> AudioProduction
|
||||
| "Cast" -> Cast
|
||||
| "Community" -> Community
|
||||
| "Creative Direction" -> CreativeDirection
|
||||
| "Misc." -> Misc
|
||||
| "Video Post-Production" -> VideoPostProduction
|
||||
| "Video Production" -> VideoProduction
|
||||
| "Visuals" -> Visuals
|
||||
| "Writing" -> Writing
|
||||
| _ -> invalidArg (nameof group) $"{group} is not a valid group"
|
||||
|
||||
/// <inheritdoc />
|
||||
override this.ToString() =
|
||||
match this with
|
||||
| Administration -> "Administration"
|
||||
| AudioPostProduction -> "Audio Post-Production"
|
||||
| AudioProduction -> "Audio Production"
|
||||
| Cast -> "Cast"
|
||||
| Community -> "Community"
|
||||
| CreativeDirection -> "Creative Direction"
|
||||
| Misc -> "Misc."
|
||||
| VideoPostProduction -> "Video Post-Production"
|
||||
| VideoProduction -> "Video Production"
|
||||
| Visuals -> "Visuals"
|
||||
| Writing -> "Writing"
|
||||
|
||||
|
||||
/// <summary>The role a person plays for a podcast or a podcast episode</summary>
|
||||
type PersonRole =
|
||||
| Announcer
|
||||
| AssistantCamera
|
||||
| AssistantDirector
|
||||
| AssistantEditor
|
||||
| AssociateProducer
|
||||
| AudioEditor
|
||||
| AudioEngineer
|
||||
| Author
|
||||
| BookingCoordinator
|
||||
| CameraGrip
|
||||
| CameraOperator
|
||||
| CoHost
|
||||
| Composer
|
||||
| Consultant
|
||||
| ContentManager
|
||||
| CoverArtDesigner
|
||||
| CoWriter
|
||||
| CreativeDirector
|
||||
| DevelopmentProducer
|
||||
| Director
|
||||
| EditorialDirector
|
||||
| ExecutiveProducer
|
||||
| FactChecker
|
||||
| FoleyArtist
|
||||
| GraphicDesigner
|
||||
| Guest
|
||||
| GuestHost
|
||||
| GuestWriter
|
||||
| Host
|
||||
| Intern
|
||||
| LightingDesigner
|
||||
| Logger
|
||||
| ManagingEditor
|
||||
| MarketingManager
|
||||
| MusicContributor
|
||||
| MusicProduction
|
||||
| Narrator
|
||||
| PostProductionEngineer
|
||||
| Producer
|
||||
| ProductionAssistant
|
||||
| ProductionCoordinator
|
||||
| RemoteRecordingEngineer
|
||||
| Reporter
|
||||
| Researcher
|
||||
| SalesManager
|
||||
| SalesRepresentative
|
||||
| ScriptCoordinator
|
||||
| ScriptEditor
|
||||
| SeniorProducer
|
||||
| SocialMediaManager
|
||||
| Songwriter
|
||||
| SoundDesigner
|
||||
| StoryEditor
|
||||
| StudioCoordinator
|
||||
| TechnicalDirector
|
||||
| TechnicalManager
|
||||
| ThemeMusic
|
||||
| Transcriber
|
||||
| Translator
|
||||
| VideoEditor
|
||||
| VoiceActor
|
||||
| Writer
|
||||
| WritingEditor
|
||||
|
||||
/// <summary>Parse a string into a role</summary>
|
||||
/// <param name="role">The string representation of the role</param>
|
||||
/// <returns>The <c>PersonRole</c> instance parsed from the string</returns>
|
||||
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
|
||||
static member Parse role =
|
||||
match role with
|
||||
| "Announcer" -> Announcer
|
||||
| "Assistant Camera" -> AssistantCamera
|
||||
| "Assistant Director" -> AssistantDirector
|
||||
| "Assistant Editor" -> AssistantEditor
|
||||
| "Associate Producer" -> AssociateProducer
|
||||
| "Audio Editor" -> AudioEditor
|
||||
| "Audio Engineer" -> AudioEngineer
|
||||
| "Author" -> Author
|
||||
| "Booking Coordinator" -> BookingCoordinator
|
||||
| "Camera Grip" -> CameraGrip
|
||||
| "Camera Operator" -> CameraOperator
|
||||
| "Co-Host" -> CoHost
|
||||
| "Composer" -> Composer
|
||||
| "Consultant" -> Consultant
|
||||
| "Content Manager" -> ContentManager
|
||||
| "Cover Art Designer" -> CoverArtDesigner
|
||||
| "Co-Writer" -> CoWriter
|
||||
| "Creative Director" -> CreativeDirector
|
||||
| "Development Producer" -> DevelopmentProducer
|
||||
| "Director" -> Director
|
||||
| "Editorial Director" -> EditorialDirector
|
||||
| "Executive Producer" -> ExecutiveProducer
|
||||
| "Fact Checker" -> FactChecker
|
||||
| "Foley Artist" -> FoleyArtist
|
||||
| "Graphic Designer" -> GraphicDesigner
|
||||
| "Guest" -> Guest
|
||||
| "Guest Host" -> GuestHost
|
||||
| "Guest Writer" -> GuestWriter
|
||||
| "Host" -> Host
|
||||
| "Intern" -> Intern
|
||||
| "Lighting Designer" -> LightingDesigner
|
||||
| "Logger" -> Logger
|
||||
| "Managing Editor" -> ManagingEditor
|
||||
| "Marketing Manager" -> MarketingManager
|
||||
| "Music Contributor" -> MusicContributor
|
||||
| "Music Production" -> MusicProduction
|
||||
| "Narrator" -> Narrator
|
||||
| "Post Production Engineer" -> PostProductionEngineer
|
||||
| "Producer" -> Producer
|
||||
| "Production Assistant" -> ProductionAssistant
|
||||
| "Production Coordinator" -> ProductionCoordinator
|
||||
| "Remote Recording Engineer" -> RemoteRecordingEngineer
|
||||
| "Reporter" -> Reporter
|
||||
| "Researcher" -> Researcher
|
||||
| "Sales Manager" -> SalesManager
|
||||
| "Sales Representative" -> SalesRepresentative
|
||||
| "Script Coordinator" -> ScriptCoordinator
|
||||
| "Script Editor" -> ScriptEditor
|
||||
| "Senior Producer" -> SeniorProducer
|
||||
| "Social Media Manager" -> SocialMediaManager
|
||||
| "Songwriter" -> Songwriter
|
||||
| "Sound Designer" -> SoundDesigner
|
||||
| "Story Editor" -> StoryEditor
|
||||
| "Studio Coordinator" -> StudioCoordinator
|
||||
| "Technical Director" -> TechnicalDirector
|
||||
| "Technical Manager" -> TechnicalManager
|
||||
| "Theme Music" -> ThemeMusic
|
||||
| "Transcriber" -> Transcriber
|
||||
| "Translator" -> Translator
|
||||
| "Editor (Video)" -> VideoEditor
|
||||
| "Voice Actor" -> VoiceActor
|
||||
| "Writer" -> Writer
|
||||
| "Editor (Writing)" -> WritingEditor
|
||||
| _ -> invalidArg (nameof role) $"{role} is not a valid role"
|
||||
|
||||
/// <inheritdoc />
|
||||
override this.ToString() =
|
||||
match this with
|
||||
| Announcer -> "Announcer"
|
||||
| AssistantCamera -> "Assistant Camera"
|
||||
| AssistantDirector -> "Assistant Director"
|
||||
| AssistantEditor -> "Assistant Editor"
|
||||
| AssociateProducer -> "Associate Producer"
|
||||
| AudioEditor -> "Audio Editor"
|
||||
| AudioEngineer -> "Audio Engineer"
|
||||
| Author -> "Author"
|
||||
| BookingCoordinator -> "Booking Coordinator"
|
||||
| CameraGrip -> "Camera Grip"
|
||||
| CameraOperator -> "Camera Operator"
|
||||
| CoHost -> "Co-Host"
|
||||
| Composer -> "Composer"
|
||||
| Consultant -> "Consultant"
|
||||
| ContentManager -> "Content Manager"
|
||||
| CoverArtDesigner -> "Cover Art Designer"
|
||||
| CoWriter -> "Co-Writer"
|
||||
| CreativeDirector -> "Creative Director"
|
||||
| DevelopmentProducer -> "Development Producer"
|
||||
| Director -> "Director"
|
||||
| EditorialDirector -> "Editorial Director"
|
||||
| ExecutiveProducer -> "Executive Producer"
|
||||
| FactChecker -> "Fact Checker"
|
||||
| FoleyArtist -> "Foley Artist"
|
||||
| GraphicDesigner -> "Graphic Designer"
|
||||
| Guest -> "Guest"
|
||||
| GuestHost -> "Guest Host"
|
||||
| GuestWriter -> "Guest Writer"
|
||||
| Host -> "Host"
|
||||
| Intern -> "Intern"
|
||||
| LightingDesigner -> "Lighting Designer"
|
||||
| Logger -> "Logger"
|
||||
| ManagingEditor -> "Managing Editor"
|
||||
| MarketingManager -> "Marketing Manager"
|
||||
| MusicContributor -> "Music Contributor"
|
||||
| MusicProduction -> "Music Production"
|
||||
| Narrator -> "Narrator"
|
||||
| PostProductionEngineer -> "Post Production Engineer"
|
||||
| Producer -> "Producer"
|
||||
| ProductionAssistant -> "Production Assistant"
|
||||
| ProductionCoordinator -> "Production Coordinator"
|
||||
| RemoteRecordingEngineer -> "Remote Recording Engineer"
|
||||
| Reporter -> "Reporter"
|
||||
| Researcher -> "Researcher"
|
||||
| SalesManager -> "Sales Manager"
|
||||
| SalesRepresentative -> "Sales Representative"
|
||||
| ScriptCoordinator -> "Script Coordinator"
|
||||
| ScriptEditor -> "Script Editor"
|
||||
| SeniorProducer -> "Senior Producer"
|
||||
| SocialMediaManager -> "Social Media Manager"
|
||||
| Songwriter -> "Songwriter"
|
||||
| SoundDesigner -> "Sound Designer"
|
||||
| StoryEditor -> "Story Editor"
|
||||
| StudioCoordinator -> "Studio Coordinator"
|
||||
| TechnicalDirector -> "Technical Director"
|
||||
| TechnicalManager -> "Technical Manager"
|
||||
| ThemeMusic -> "Theme Music"
|
||||
| Transcriber -> "Transcriber"
|
||||
| Translator -> "Translator"
|
||||
| VideoEditor -> "Editor (Video)"
|
||||
| VoiceActor -> "Voice Actor"
|
||||
| Writer -> "Writer"
|
||||
| WritingEditor -> "Editor (Writing)"
|
||||
|
||||
/// <summary>Get the display name of the role</summary>
|
||||
/// <remarks>Both Video Production and Writing have an "Editor"; this is how it should be rendered in RSS feeds and
|
||||
/// in the UI</remarks>
|
||||
member this.ToDisplay() =
|
||||
match this with VideoEditor | WritingEditor -> "Editor" | _ -> string this
|
||||
|
||||
/// <summary>Role / group cross-reference</summary>
|
||||
static member GroupXref =
|
||||
[ Announcer, Cast
|
||||
AssistantCamera, VideoProduction
|
||||
AssistantDirector, CreativeDirection
|
||||
AssistantEditor, VideoPostProduction
|
||||
AssociateProducer, CreativeDirection
|
||||
AudioEditor, AudioPostProduction
|
||||
AudioEngineer, AudioProduction
|
||||
Author, Writing
|
||||
BookingCoordinator, Administration
|
||||
CameraGrip, VideoProduction
|
||||
CameraOperator, VideoProduction
|
||||
CoHost, Cast
|
||||
Composer, AudioPostProduction
|
||||
ContentManager, Administration
|
||||
Consultant, Misc
|
||||
CoverArtDesigner, Visuals
|
||||
CoWriter, Writing
|
||||
CreativeDirector, CreativeDirection
|
||||
DevelopmentProducer, CreativeDirection
|
||||
Director, CreativeDirection
|
||||
EditorialDirector, Writing
|
||||
ExecutiveProducer, CreativeDirection
|
||||
FactChecker, Writing
|
||||
FoleyArtist, AudioPostProduction
|
||||
GraphicDesigner, Visuals
|
||||
Guest, Cast
|
||||
GuestHost, Cast
|
||||
GuestWriter, Writing
|
||||
Host, Cast
|
||||
Intern, Misc
|
||||
LightingDesigner, VideoProduction
|
||||
Logger, Writing
|
||||
ManagingEditor, Writing
|
||||
MarketingManager, Administration
|
||||
MusicContributor, AudioPostProduction
|
||||
MusicProduction, AudioPostProduction
|
||||
Narrator, Cast
|
||||
PostProductionEngineer, AudioProduction
|
||||
Producer, CreativeDirection
|
||||
ProductionAssistant, Administration
|
||||
ProductionCoordinator, Administration
|
||||
RemoteRecordingEngineer, AudioProduction
|
||||
Reporter, Cast
|
||||
Researcher, Writing
|
||||
SalesManager, Administration
|
||||
SalesRepresentative, Administration
|
||||
ScriptCoordinator, Writing
|
||||
ScriptEditor, Writing
|
||||
SeniorProducer, CreativeDirection
|
||||
SocialMediaManager, Community
|
||||
Songwriter, Writing
|
||||
SoundDesigner, AudioPostProduction
|
||||
StoryEditor, Writing
|
||||
StudioCoordinator, AudioProduction
|
||||
TechnicalDirector, AudioProduction
|
||||
TechnicalManager, AudioProduction
|
||||
ThemeMusic, AudioPostProduction
|
||||
Transcriber, Writing
|
||||
Translator, Writing
|
||||
VideoEditor, VideoPostProduction
|
||||
VoiceActor, Cast
|
||||
Writer, Writing
|
||||
WritingEditor, Writing ]
|
||||
|
||||
|
||||
open NodaTime.Text
|
||||
|
||||
/// <summary>A podcast episode</summary>
|
||||
type Episode = {
|
||||
/// <summary>The URL to the media file for the episode (may be permalink)</summary>
|
||||
Media: string
|
||||
|
||||
/// <summary>The length of the media file, in bytes</summary>
|
||||
Length: int64
|
||||
|
||||
/// <summary>The duration of the episode</summary>
|
||||
Duration: Duration option
|
||||
|
||||
/// <summary>The media type of the file (overrides podcast default if present)</summary>
|
||||
MediaType: string option
|
||||
|
||||
/// <summary>The URL to the image file for this episode (overrides podcast image if present, may be permalink)</summary>
|
||||
ImageUrl: string option
|
||||
|
||||
/// <summary>A subtitle for this episode</summary>
|
||||
Subtitle: string option
|
||||
|
||||
/// <summary>This episode's explicit rating (overrides podcast rating if present)</summary>
|
||||
Explicit: ExplicitRating option
|
||||
|
||||
/// <summary>Chapters for this episode</summary>
|
||||
Chapters: Chapter list option
|
||||
|
||||
/// <summary>A link to a chapter file</summary>
|
||||
ChapterFile: string option
|
||||
|
||||
/// <summary>The MIME type for the chapter file</summary>
|
||||
ChapterType: string option
|
||||
|
||||
/// <summary>Whether the chapters have locations that should be displayed as waypoints</summary>
|
||||
ChapterWaypoints: bool option
|
||||
|
||||
/// <summary>The URL for the transcript of the episode (may be permalink)</summary>
|
||||
TranscriptUrl: string option
|
||||
|
||||
/// <summary>The MIME type of the transcript</summary>
|
||||
TranscriptType: string option
|
||||
|
||||
/// <summary>The language in which the transcript is written</summary>
|
||||
TranscriptLang: string option
|
||||
|
||||
/// <summary>If true, the transcript will be declared (in the feed) to be a captions file</summary>
|
||||
TranscriptCaptions: bool option
|
||||
|
||||
/// <summary>The season number (for serialized podcasts)</summary>
|
||||
SeasonNumber: int option
|
||||
|
||||
/// <summary>A description of the season</summary>
|
||||
SeasonDescription: string option
|
||||
|
||||
/// <summary>The episode number</summary>
|
||||
EpisodeNumber: double option
|
||||
|
||||
/// <summary>A description of the episode</summary>
|
||||
EpisodeDescription: string option
|
||||
} with
|
||||
|
||||
/// <summary>An empty episode</summary>
|
||||
static member Empty =
|
||||
{ Media = ""
|
||||
Length = 0L
|
||||
Duration = None
|
||||
MediaType = None
|
||||
ImageUrl = None
|
||||
Subtitle = None
|
||||
Explicit = None
|
||||
Chapters = None
|
||||
ChapterFile = None
|
||||
ChapterType = None
|
||||
ChapterWaypoints = None
|
||||
TranscriptUrl = None
|
||||
TranscriptType = None
|
||||
TranscriptLang = None
|
||||
TranscriptCaptions = None
|
||||
SeasonNumber = None
|
||||
SeasonDescription = None
|
||||
EpisodeNumber = None
|
||||
EpisodeDescription = None }
|
||||
|
||||
/// <summary>Format a duration for an episode</summary>
|
||||
/// <returns>A duration formatted in hours, minutes, and seconds</returns>
|
||||
member this.FormatDuration() =
|
||||
this.Duration |> Option.map (DurationPattern.CreateWithInvariantCulture("H:mm:ss").Format)
|
||||
|
||||
|
||||
/// <summary>Types of markup text</summary>
|
||||
type MarkupText =
|
||||
/// <summary>Markdown text</summary>
|
||||
@@ -1069,44 +583,6 @@ type PageId =
|
||||
match this with PageId it -> it
|
||||
|
||||
|
||||
/// <summary>PodcastIndex.org podcast:medium allowed values</summary>
|
||||
[<Struct>]
|
||||
type PodcastMedium =
|
||||
| Podcast
|
||||
| Music
|
||||
| Video
|
||||
| Film
|
||||
| Audiobook
|
||||
| Newsletter
|
||||
| Blog
|
||||
|
||||
/// <summary>Parse a string into a podcast medium</summary>
|
||||
/// <param name="medium">The string to be parsed</param>
|
||||
/// <returns>The <c>PodcastMedium</c> parsed from the string</returns>
|
||||
/// <exception cref="InvalidArgumentException">If the string is not valid</exception>
|
||||
static member Parse medium =
|
||||
match medium with
|
||||
| "podcast" -> Podcast
|
||||
| "music" -> Music
|
||||
| "video" -> Video
|
||||
| "film" -> Film
|
||||
| "audiobook" -> Audiobook
|
||||
| "newsletter" -> Newsletter
|
||||
| "blog" -> Blog
|
||||
| _ -> invalidArg (nameof medium) $"{medium} is not a valid podcast medium"
|
||||
|
||||
/// <inheritdoc />
|
||||
override this.ToString() =
|
||||
match this with
|
||||
| Podcast -> "podcast"
|
||||
| Music -> "music"
|
||||
| Video -> "video"
|
||||
| Film -> "film"
|
||||
| Audiobook -> "audiobook"
|
||||
| Newsletter -> "newsletter"
|
||||
| Blog -> "blog"
|
||||
|
||||
|
||||
/// <summary>Statuses for posts</summary>
|
||||
[<Struct>]
|
||||
type PostStatus =
|
||||
|
||||
Reference in New Issue
Block a user