spektrix_uk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +10 -0
- data/COMM-LICENSE +22 -0
- data/LICENSE +617 -0
- data/README.md +181 -0
- data/lib/spektrix_uk/cinema.rb +87 -0
- data/lib/spektrix_uk/internal/api.rb +45 -0
- data/lib/spektrix_uk/internal/http.rb +27 -0
- data/lib/spektrix_uk/internal/parser/instance.rb +87 -0
- data/lib/spektrix_uk/internal/title_sanitizer.rb +76 -0
- data/lib/spektrix_uk/internal/venue.rb +195 -0
- data/lib/spektrix_uk/performance.rb +82 -0
- data/lib/spektrix_uk/version.rb +6 -0
- data/lib/spektrix_uk.rb +19 -0
- metadata +153 -0
data/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# SpektrixUk
|
|
2
|
+
|
|
3
|
+
A simple gem to pull cinemas and performances from UK independent cinemas that sell tickets through [Spektrix](https://www.spektrix.com) and spit out useful formatted info.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
gem 'spektrix_uk'
|
|
10
|
+
|
|
11
|
+
And then execute:
|
|
12
|
+
|
|
13
|
+
$ bundle
|
|
14
|
+
|
|
15
|
+
Or install it yourself as:
|
|
16
|
+
|
|
17
|
+
$ gem install spektrix_uk
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
The gem conforms to the API set down in the `cinebase` gem, [andycroll/cinebase](https://github.com/andycroll/cinebase), which provides a lot of useful base vocabulary and repetitive code for this series of cinema focussed gems.
|
|
22
|
+
|
|
23
|
+
Performance titles are sanitized as much as possible, removing strands,
|
|
24
|
+
certificates, notes in square brackets, extras such as food or a Q&A, and
|
|
25
|
+
standardizing event cinema names.
|
|
26
|
+
|
|
27
|
+
No token or setup is needed. Each venue has its own Spektrix account, its
|
|
28
|
+
client, and the public Spektrix API v3 at
|
|
29
|
+
`https://system.spektrix.com/<client>/api/v3` lists its events and their
|
|
30
|
+
instances. Each account names its attributes differently, so the gem holds a
|
|
31
|
+
small configuration for each venue.
|
|
32
|
+
|
|
33
|
+
| id | Cinema | Client |
|
|
34
|
+
|------------|-------------------------|-------------------------|
|
|
35
|
+
| `broadway` | Broadway, Nottingham | `broadwaynottingham` |
|
|
36
|
+
| `home` | HOME, Manchester | `home` |
|
|
37
|
+
| `hpph` | Hyde Park Picture House | `leedsheritagetheatres` |
|
|
38
|
+
| `showroom` | Showroom Cinema | `showroomworkstation` |
|
|
39
|
+
| `tyneside` | Tyneside Cinema | `tynesidecinema` |
|
|
40
|
+
|
|
41
|
+
The ids belong to the gem and never change.
|
|
42
|
+
|
|
43
|
+
### Cinema
|
|
44
|
+
|
|
45
|
+
Cinemas, addresses and coordinates are held in the gem, so they make no
|
|
46
|
+
requests. Every cinema's brand is `'Independent'`.
|
|
47
|
+
|
|
48
|
+
``` ruby
|
|
49
|
+
SpektrixUk::Cinema.all
|
|
50
|
+
#=> [<SpektrixUk::Cinema ...>, <SpektrixUk::Cinema ...>, ...]
|
|
51
|
+
|
|
52
|
+
cinema = SpektrixUk::Cinema.new('tyneside')
|
|
53
|
+
#=> <SpektrixUk::Cinema ...>
|
|
54
|
+
|
|
55
|
+
cinema.adr
|
|
56
|
+
#=> {
|
|
57
|
+
street_address: '10 Pilgrim Street',
|
|
58
|
+
extended_address: nil,
|
|
59
|
+
locality: 'Newcastle upon Tyne',
|
|
60
|
+
region: nil,
|
|
61
|
+
postal_code: 'NE1 6QG',
|
|
62
|
+
country_name: 'United Kingdom'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
cinema.brand
|
|
66
|
+
#=> 'Independent'
|
|
67
|
+
|
|
68
|
+
cinema.name
|
|
69
|
+
#=> 'Tyneside Cinema'
|
|
70
|
+
|
|
71
|
+
cinema.id
|
|
72
|
+
#=> 'tyneside'
|
|
73
|
+
|
|
74
|
+
cinema.latitude
|
|
75
|
+
#=> 54.973903
|
|
76
|
+
|
|
77
|
+
cinema.url
|
|
78
|
+
#=> 'https://tynesidecinema.co.uk'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Performances
|
|
82
|
+
|
|
83
|
+
`Performance.at` returns performances for an inclusive range of showing dates,
|
|
84
|
+
defaulting to today in London and the following 13 days. Any range takes two
|
|
85
|
+
requests: the events with an instance in the range, and the instances. The API
|
|
86
|
+
sets no limit on the range. The Leeds Heritage Theatres account answers slowly,
|
|
87
|
+
in about half a minute for a year.
|
|
88
|
+
|
|
89
|
+
``` ruby
|
|
90
|
+
SpektrixUk::Performance.at('tyneside', from: Date.today, to: Date.today + 2)
|
|
91
|
+
#=> [<SpektrixUk::Performance ...>, <SpektrixUk::Performance ...>, ...]
|
|
92
|
+
|
|
93
|
+
performance = SpektrixUk::Performance.at('tyneside').first
|
|
94
|
+
#=> <SpektrixUk::Performance ...>
|
|
95
|
+
|
|
96
|
+
performance.film_name
|
|
97
|
+
#=> 'The Grinch'
|
|
98
|
+
|
|
99
|
+
performance.film_year
|
|
100
|
+
#=> 2018 # from the venue's year attribute, or the title
|
|
101
|
+
|
|
102
|
+
performance.dimension
|
|
103
|
+
#=> '2d'
|
|
104
|
+
|
|
105
|
+
performance.variant
|
|
106
|
+
#=> ['audio_described', 'relaxed', 'subtitled']
|
|
107
|
+
|
|
108
|
+
performance.starting_at
|
|
109
|
+
#=> 2026-12-20 10:30:00 UTC
|
|
110
|
+
|
|
111
|
+
performance.booking_url
|
|
112
|
+
#=> 'https://tynesidecinema.co.uk/book-online/1808202'
|
|
113
|
+
|
|
114
|
+
performance.cinema_name
|
|
115
|
+
#=> 'Tyneside Cinema'
|
|
116
|
+
|
|
117
|
+
performance.cinema_id
|
|
118
|
+
#=> 'tyneside'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Only films are listed, including event cinema and screenings with a Q&A. Each
|
|
122
|
+
venue marks films in its own way:
|
|
123
|
+
|
|
124
|
+
| id | Films are events where |
|
|
125
|
+
|------------|---------------------------------------------------------------------|
|
|
126
|
+
| `broadway` | `Film` or `EventCinema` is true |
|
|
127
|
+
| `home` | `ShowType` is `Film` |
|
|
128
|
+
| `hpph` | `LiveOrFilm` is `Film`, `Website` is `HPPH` and `Venue` is the Picture House or blank |
|
|
129
|
+
| `showroom` | `ShowroomNonFilmEvent` is not true |
|
|
130
|
+
| `tyneside` | `Medium` is not `Non-Film` |
|
|
131
|
+
|
|
132
|
+
Passes, bundles and lectures are left out by name. Cancelled instances are left
|
|
133
|
+
out. Instances at midnight are left out, as Hyde Park Picture House lists films
|
|
134
|
+
at midnight on the last day of the month before their times are set. Instances
|
|
135
|
+
not on sale are kept: most have started or are not yet on sale.
|
|
136
|
+
|
|
137
|
+
Booking links go to each venue's own booking page:
|
|
138
|
+
|
|
139
|
+
| id | Booking link |
|
|
140
|
+
|------------|---------------------------------------------------------------------------------|
|
|
141
|
+
| `broadway` | `https://www.broadway.org.uk/book/<instance>` |
|
|
142
|
+
| `home` | `https://tickets.homemcr.org/home/website/ChooseSeats.aspx?EventInstanceId=<instance>` |
|
|
143
|
+
| `hpph` | `https://tickets.hpph.co.uk/leedsheritagetheatres/website/ChooseSeats.aspx?EventInstanceId=<instance>` |
|
|
144
|
+
| `showroom` | `https://tickets.showroomcinema.org.uk/showroomworkstation/website/ChooseSeats.aspx?EventInstanceId=<instance>` |
|
|
145
|
+
| `tyneside` | `https://tynesidecinema.co.uk/book-online/<instance>` |
|
|
146
|
+
|
|
147
|
+
The instance is the number that starts a Spektrix instance id. Showroom and Hyde
|
|
148
|
+
Park Picture House open these pages in a frame on their own sites.
|
|
149
|
+
|
|
150
|
+
Variants are `35mm`, `arts`, `audio_described`, `baby`, `kids`, `q&a`,
|
|
151
|
+
`relaxed`, `senior` and `subtitled`, from each venue's event and instance
|
|
152
|
+
attributes and from titles. `subtitled` means captions or subtitles for
|
|
153
|
+
people who are deaf or hard of hearing, not a foreign language film's
|
|
154
|
+
subtitles.
|
|
155
|
+
|
|
156
|
+
## Tests
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
bundle exec rake # unit tests against fixtures
|
|
160
|
+
bundle exec rake live # live Spektrix API
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Fixtures in `test/fixtures` are API responses captured from each venue's
|
|
164
|
+
account and trimmed to a few events. `test/support/api_shapes.rb` describes the
|
|
165
|
+
fields and attributes the gem reads. The unit suite checks the fixtures against
|
|
166
|
+
it and the live suite checks real responses, held only in memory. When an
|
|
167
|
+
account changes, update the shape first, then the fixtures.
|
|
168
|
+
|
|
169
|
+
## Releasing
|
|
170
|
+
|
|
171
|
+
Bump the version, merge, then run the **Release Gem** workflow in GitHub
|
|
172
|
+
Actions. It runs the tests, tags the version and publishes the gem through
|
|
173
|
+
RubyGems trusted publishing.
|
|
174
|
+
|
|
175
|
+
## Contributing
|
|
176
|
+
|
|
177
|
+
1. Fork it
|
|
178
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
179
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
180
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
181
|
+
5. Create new Pull Request
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module SpektrixUk
|
|
2
|
+
# An independent cinema that sells tickets through Spektrix
|
|
3
|
+
class Cinema < Cinebase::Cinema
|
|
4
|
+
# @!attribute [r] id
|
|
5
|
+
# @return [String] the gem's id of the cinema, e.g. 'tyneside'
|
|
6
|
+
|
|
7
|
+
# Constructor
|
|
8
|
+
# @param [String, Symbol] id the cinema id, e.g. 'tyneside'
|
|
9
|
+
# @return [SpektrixUk::Cinema]
|
|
10
|
+
def initialize(id)
|
|
11
|
+
@id = id.to_s
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Every supported cinema, held in the gem
|
|
15
|
+
# @return [Array<SpektrixUk::Cinema>]
|
|
16
|
+
# @example
|
|
17
|
+
# SpektrixUk::Cinema.all
|
|
18
|
+
# #=> [<SpektrixUk::Cinema>, <SpektrixUk::Cinema>, ...]
|
|
19
|
+
def self.all
|
|
20
|
+
SpektrixUk::Internal::Venue::ALL.keys.map { |id| new(id) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @!method address
|
|
24
|
+
# Address of the cinema
|
|
25
|
+
# @return [Hash] of different address parts
|
|
26
|
+
# @see #adr
|
|
27
|
+
|
|
28
|
+
# Address of the cinema
|
|
29
|
+
# @return [Hash] contains :street_address, :extended_address,
|
|
30
|
+
# :locality, :region, :postal_code, :country_name
|
|
31
|
+
# @example
|
|
32
|
+
# cinema = SpektrixUk::Cinema.new('tyneside')
|
|
33
|
+
# cinema.adr
|
|
34
|
+
# #=> {
|
|
35
|
+
# street_address: '10 Pilgrim Street',
|
|
36
|
+
# extended_address: nil,
|
|
37
|
+
# locality: 'Newcastle upon Tyne',
|
|
38
|
+
# region: nil,
|
|
39
|
+
# postal_code: 'NE1 6QG',
|
|
40
|
+
# country_name: 'United Kingdom'
|
|
41
|
+
# }
|
|
42
|
+
# @note Uses method naming as at http://microformats.org/wiki/adr
|
|
43
|
+
def adr
|
|
44
|
+
venue.address
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Brand of the cinema
|
|
48
|
+
# @return [String] which will always be 'Independent'
|
|
49
|
+
def brand
|
|
50
|
+
'Independent'.freeze
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @return [Float] latitude of the cinema
|
|
54
|
+
def latitude
|
|
55
|
+
venue.latitude
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [Float] longitude of the cinema
|
|
59
|
+
def longitude
|
|
60
|
+
venue.longitude
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The name of the cinema
|
|
64
|
+
# @return [String]
|
|
65
|
+
# @example
|
|
66
|
+
# SpektrixUk::Cinema.new('tyneside').name
|
|
67
|
+
# #=> 'Tyneside Cinema'
|
|
68
|
+
def name
|
|
69
|
+
venue.name
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The cinema's own website
|
|
73
|
+
# @return [String]
|
|
74
|
+
# @example
|
|
75
|
+
# SpektrixUk::Cinema.new('tyneside').url
|
|
76
|
+
# #=> 'https://tynesidecinema.co.uk'
|
|
77
|
+
def url
|
|
78
|
+
venue.url
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def venue
|
|
84
|
+
SpektrixUk::Internal::Venue.find(id)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module SpektrixUk
|
|
6
|
+
# Internal utility classes: Do not use
|
|
7
|
+
# @api private
|
|
8
|
+
module Internal
|
|
9
|
+
# Reads a venue's events and instances from the public Spektrix API v3,
|
|
10
|
+
# which needs no token. Each venue's Spektrix account is its client.
|
|
11
|
+
class Api
|
|
12
|
+
include Http
|
|
13
|
+
|
|
14
|
+
BASE_URL = "https://system.spektrix.com"
|
|
15
|
+
|
|
16
|
+
# @param [String] client the Spektrix client name, e.g. 'tynesidecinema'
|
|
17
|
+
def initialize(client)
|
|
18
|
+
@client = client
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Events with an instance starting within the range
|
|
22
|
+
# @param [Date] from first day
|
|
23
|
+
# @param [Date] to the day after the last, as the API reads a date as
|
|
24
|
+
# its midnight
|
|
25
|
+
# @return [Array<Hash>]
|
|
26
|
+
def events(from, to)
|
|
27
|
+
get_json("events", instanceStart_from: from.iso8601, instanceStart_to: to.iso8601)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Instances, or performances, starting within the range
|
|
31
|
+
# @param [Date] from first day
|
|
32
|
+
# @param [Date] to the day after the last
|
|
33
|
+
# @return [Array<Hash>]
|
|
34
|
+
def instances(from, to)
|
|
35
|
+
get_json("instances", startFrom: from.iso8601, startTo: to.iso8601)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def get_json(resource, query)
|
|
41
|
+
JSON.parse(get("#{BASE_URL}/#{@client}/api/v3/#{resource}?#{URI.encode_www_form(query)}"))
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
|
|
5
|
+
module SpektrixUk
|
|
6
|
+
# Internal utility classes: Do not use
|
|
7
|
+
# @api private
|
|
8
|
+
module Internal
|
|
9
|
+
# Requests to the Spektrix API
|
|
10
|
+
module Http
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def get(url)
|
|
14
|
+
uri = URI(url)
|
|
15
|
+
request = Net::HTTP::Get.new(uri)
|
|
16
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true,
|
|
17
|
+
open_timeout: 10, read_timeout: 120) do |http|
|
|
18
|
+
http.request(request)
|
|
19
|
+
end
|
|
20
|
+
# Omit response bodies from errors, and never follow redirects.
|
|
21
|
+
raise "Spektrix HTTP error #{response.code}" unless response.is_a?(Net::HTTPSuccess)
|
|
22
|
+
|
|
23
|
+
String.new(response.body, encoding: Encoding::UTF_8)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module SpektrixUk
|
|
6
|
+
# @api private
|
|
7
|
+
module Internal
|
|
8
|
+
module Parser
|
|
9
|
+
# Parses a Spektrix instance of a film into performance attributes
|
|
10
|
+
class Instance
|
|
11
|
+
# Variants a title names, at any venue
|
|
12
|
+
TITLE_VARIANTS = {
|
|
13
|
+
/\bQ&A\b/i => "q&a",
|
|
14
|
+
/\b35mm\b/i => "35mm",
|
|
15
|
+
/\A(?:Baby Kino|Baby & Carer\/)|Parent & Baby/i => "baby",
|
|
16
|
+
/\b(?:Toddler Kino|Family Time|Family Film)\b|\A(?:TK|FT):/ => "kids",
|
|
17
|
+
/\bRelaxed Screening\b/i => "relaxed"
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
THREE_D = /\b3D\b/
|
|
21
|
+
YEAR = /\A\d{4}\z/
|
|
22
|
+
TIME_ZONE = "Europe/London"
|
|
23
|
+
|
|
24
|
+
# @param [Hash] instance one Spektrix instance
|
|
25
|
+
# @param [Hash] event the event the instance belongs to
|
|
26
|
+
# @param [SpektrixUk::Internal::Venue] venue the venue that sells it
|
|
27
|
+
def initialize(instance, event:, venue:)
|
|
28
|
+
@instance = instance
|
|
29
|
+
@event = event
|
|
30
|
+
@venue = venue
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Hash] options for SpektrixUk::Performance.new
|
|
34
|
+
def to_hash
|
|
35
|
+
{
|
|
36
|
+
booking_url: format(@venue.booking_url, instance: @instance.fetch("id")[/\A\d+/]),
|
|
37
|
+
dimension: three_d? ? "3d" : "2d",
|
|
38
|
+
festival: Cinebase::Festival.from_title(title, year: starting_at.year),
|
|
39
|
+
film_name: TitleSanitizer.new(title).sanitized,
|
|
40
|
+
film_year: film_year,
|
|
41
|
+
starting_at: starting_at,
|
|
42
|
+
variant: variant
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def film_year
|
|
49
|
+
year = @event[@venue.year].to_s.strip
|
|
50
|
+
if year.match?(YEAR)
|
|
51
|
+
year.to_i
|
|
52
|
+
else
|
|
53
|
+
Cinebase::TitleSanitizer.film_year(title.gsub(TitleSanitizer::NOTES, ""))
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# startUtc is UTC though it carries no zone
|
|
58
|
+
def starting_at
|
|
59
|
+
@starting_at ||=
|
|
60
|
+
if @instance["startUtc"]
|
|
61
|
+
Time.iso8601(@instance["startUtc"].delete_suffix("Z") + "Z")
|
|
62
|
+
else
|
|
63
|
+
local = Time.iso8601("#{@instance.fetch('start')}Z")
|
|
64
|
+
TZInfo::Timezone.get(TIME_ZONE).local_to_utc(local) { |periods| periods.first }.utc
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def three_d?
|
|
69
|
+
@instance[@venue.three_d] == true || title.match?(THREE_D)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def title
|
|
73
|
+
@title ||= @event.fetch("name").strip
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def variant
|
|
77
|
+
attributes = [[@venue.event_variants, @event], [@venue.instance_variants, @instance]]
|
|
78
|
+
variants = attributes.flat_map do |map, record|
|
|
79
|
+
map.filter_map { |attribute, values| values[record[attribute]] }
|
|
80
|
+
end
|
|
81
|
+
variants += TITLE_VARIANTS.filter_map { |pattern, name| name if title.match?(pattern) }
|
|
82
|
+
variants.uniq
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module SpektrixUk
|
|
2
|
+
# Internal utility classes: Do not use
|
|
3
|
+
# @api private
|
|
4
|
+
module Internal
|
|
5
|
+
# Sanitize and standardize film titles
|
|
6
|
+
class TitleSanitizer < Cinebase::TitleSanitizer
|
|
7
|
+
# Square-bracketed notes, e.g. '[35mm]', '[+ Filmmaker Q&A]' or
|
|
8
|
+
# Tyneside's programme year '[2026]'
|
|
9
|
+
NOTES = /\s*\[[^\]]*\]/
|
|
10
|
+
|
|
11
|
+
# Strands and seasons named before a colon, e.g. 'Classics: Dracula'
|
|
12
|
+
STRANDS = [
|
|
13
|
+
'Baby Kino', 'BHM\d+', 'Classics', 'Cult', 'DocNights', 'Dog Friendly', 'Early Doors(?: - \w+)?',
|
|
14
|
+
'Family Time', 'FF[FS]\d+(?: x [^:]+)?', 'Film Studies', 'FT', 'Healthy Holidays',
|
|
15
|
+
'Into Film Festival', 'Japanese Film Club presents', 'My Movie', 'PBPB', 'Perspectives',
|
|
16
|
+
'Philosophy', 'Popcorn & Prosecco', 'Presents', 'Preview', 'Queer East \d+',
|
|
17
|
+
'Queer Up Duck! Festival', 'Reclaim the Frame presents', 'Spotlight',
|
|
18
|
+
'TK', 'Toddler Kino', 'UK Jewish Film Festival', 'VHS Screening', 'WTF'
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
# Extras after a '+', e.g. 'The Holiday + Popcorn & Prosecco'
|
|
22
|
+
EXTRAS = [
|
|
23
|
+
'Event', 'Family Yoga', 'Intro(?:duction)?', 'Live Score.*', 'Mince Pie & Prosecco',
|
|
24
|
+
'Popcorn & Prosecco', 'Shorts?'
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
# Versions after a dash, e.g. 'Elf - Pay What You Feel'
|
|
28
|
+
VERSIONS = [
|
|
29
|
+
'\d+(?:st|nd|rd|th) Anniversary', '4K Restoration', 'Parent & Baby', 'Pay What You Feel',
|
|
30
|
+
'Presented by .*', 'Quiet Coach.*', 'Santa Extravaganza', 'Sing Along'
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
# @!method initialize(title)
|
|
34
|
+
# Constructor
|
|
35
|
+
# @param [String] title a film title
|
|
36
|
+
# @return [SpektrixUk::Internal::TitleSanitizer]
|
|
37
|
+
|
|
38
|
+
# @!method sanitized
|
|
39
|
+
# sanitized and standardized title
|
|
40
|
+
# @return [String] sanitised title
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# strings and regex to be removed
|
|
45
|
+
def remove
|
|
46
|
+
[
|
|
47
|
+
NOTES,
|
|
48
|
+
/\s*\((?:U|PG|12A?|15|18|R18|CTBA|TBC)\)/, # certificate
|
|
49
|
+
/\s*\((?:Preview|NEIFF|[^)]*Festival)\)/i, # preview or festival
|
|
50
|
+
/\AChristmas \d+\s*[-:]\s*/, # season
|
|
51
|
+
/\A(?!NT Live|Royal )[^\/]{1,30}\/\s+/, # strand, e.g. 'Cinema Social/ '
|
|
52
|
+
/\A(?:#{STRANDS.join('|')}):\s*/, # strand, e.g. 'Classics: '
|
|
53
|
+
/\A(?:FFS\d+|See It First –|Family Film Club -|Family Supper Club -)\s+/, # strand
|
|
54
|
+
/\A(?:Festive Afternoon Tea|Supper Club) \+\s*/, # food before the film
|
|
55
|
+
/\s+\+\s+(?:#{EXTRAS.join('|')})\z/i, # extras after the film
|
|
56
|
+
/\s*\+[^+]*\bQ&A\b.*\z/i, # Q&A, e.g. '+ Q&A with the director'
|
|
57
|
+
/\s+[-–]\s+(?:#{VERSIONS.join('|')})\z/i, # version
|
|
58
|
+
/:\s*\d+(?:st|nd|rd|th) Anniversary\z/i, # anniversary
|
|
59
|
+
/\s+Family Event\z/i, # family event
|
|
60
|
+
/\s*\(\d+(?:st|nd|rd|th) Anniversary [^)]*\)/i, # anniversary restoration
|
|
61
|
+
/\s*\(4K Restoration\)/i # restoration
|
|
62
|
+
] + COMMON_REMOVE
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# regexes and their replacements
|
|
66
|
+
def replace
|
|
67
|
+
{
|
|
68
|
+
/\A(?:EOS|Exhibition on Screen):\s*(.*)/i => 'Exhibition on Screen: ',
|
|
69
|
+
/\A(.*) - Met Opera \d+\/\d+\z/ => 'Met Opera: ',
|
|
70
|
+
/\A(?:NT Live|NTL|National Theatre Live)[:\/]\s*(.*)/ => 'National Theatre Live: ',
|
|
71
|
+
/\A(?:RBO|(?:The )?Royal (?:Ballet|Opera)(?: and Opera)?)[:\/]\s*(.*)/ => 'Royal Ballet and Opera: '
|
|
72
|
+
}.merge(COMMON_REPLACE)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|