playlist 0.1.3 → 0.2.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 +4 -4
- data/README.md +0 -1
- data/lib/playlist.rb +21 -0
- data/lib/playlist/contributor.rb +6 -0
- data/lib/playlist/format.rb +1 -1
- data/lib/playlist/format/cue.rb +117 -0
- data/lib/playlist/track.rb +49 -0
- data/lib/playlist/version.rb +1 -1
- metadata +3 -3
- data/lib/playlist/format/dira.rb +0 -57
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e0abdf1a10cd890a9d446a6617ed5bf12856dba0
|
4
|
+
data.tar.gz: 0719e57569fed6a8798e58db21eb50dd6c7dfc34
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44aa5aff176b61c1ad739765882e4c7a7dde57f828a9f4eee95bf19112362d8bcb4b55fe24145e0b435e48f56746ff6bba83086bf810862ffe584f253821cbdc
|
7
|
+
data.tar.gz: e71116d7a8906db2cea15f117a4de683945e47b7fb285a36c8043635491c94979653be85adb0b90e4fd947d7fe6626baf4823fd128e32a58414a22ee78836c6b
|
data/README.md
CHANGED
data/lib/playlist.rb
CHANGED
@@ -14,6 +14,11 @@ class Playlist
|
|
14
14
|
# @return [String]
|
15
15
|
attr_accessor :annotation
|
16
16
|
|
17
|
+
# A URI (or filename) to the location of the media
|
18
|
+
# Use this if the playlist is a single file
|
19
|
+
# @return [String]
|
20
|
+
attr_accessor :media_location
|
21
|
+
|
17
22
|
# A URL to get more inforamtion about this playlist
|
18
23
|
# @return [String]
|
19
24
|
attr_accessor :info_url
|
@@ -22,6 +27,11 @@ class Playlist
|
|
22
27
|
# @return [String]
|
23
28
|
attr_accessor :license
|
24
29
|
|
30
|
+
# Get a hash of identifier for this playlist
|
31
|
+
# Identifiers can either be Strings or URIs
|
32
|
+
# @return [Hash] an hash of identifiers
|
33
|
+
attr_reader :identifiers
|
34
|
+
|
25
35
|
# Get the array that contains the list of track for this playlist
|
26
36
|
# @return [Array<Track>] an array of tracks in the playlist
|
27
37
|
attr_reader :tracks
|
@@ -51,6 +61,17 @@ class Playlist
|
|
51
61
|
@tracks.map(&:duration).compact.inject(:+)
|
52
62
|
end
|
53
63
|
|
64
|
+
# Calculate the start track times based on the duration.
|
65
|
+
# This method will only overwrite the start times, if not set
|
66
|
+
def calculate_start_times
|
67
|
+
time = tracks.first.start_time || 0
|
68
|
+
tracks.each do |track|
|
69
|
+
break if track.duration.nil?
|
70
|
+
time = (track.start_time ||= time)
|
71
|
+
time += track.duration
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
54
75
|
autoload :Contributor, 'playlist/contributor'
|
55
76
|
autoload :Track, 'playlist/track'
|
56
77
|
autoload :Format, 'playlist/format'
|
data/lib/playlist/contributor.rb
CHANGED
@@ -6,6 +6,11 @@ class Playlist::Contributor
|
|
6
6
|
# @return [String]
|
7
7
|
attr_accessor :name
|
8
8
|
|
9
|
+
# Get a hash of identifier for this Contributor
|
10
|
+
# Identifiers can either be Strings or URIs
|
11
|
+
# @return [Hash] an hash of identifiers
|
12
|
+
attr_reader :identifiers
|
13
|
+
|
9
14
|
# The role of the contribrition to the track
|
10
15
|
#
|
11
16
|
# Recommended values for role:
|
@@ -18,6 +23,7 @@ class Playlist::Contributor
|
|
18
23
|
|
19
24
|
# Create a new Contributor
|
20
25
|
def initialize(attr = nil)
|
26
|
+
@identifiers = {}
|
21
27
|
if attr.is_a?(Hash)
|
22
28
|
attr.each_pair do |key, value|
|
23
29
|
send("#{key}=", value)
|
data/lib/playlist/format.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Base module for the various playlist formats
|
2
2
|
module Playlist::Format
|
3
|
-
autoload :
|
3
|
+
autoload :Cue, 'playlist/format/cue'
|
4
4
|
autoload :M3U, 'playlist/format/m3u'
|
5
5
|
autoload :SimpleText, 'playlist/format/simple_text'
|
6
6
|
autoload :XSPF, 'playlist/format/xspf'
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# Module to parse and generate Cue Sheet file format
|
2
|
+
module Playlist::Format::Cue
|
3
|
+
class << self
|
4
|
+
# Parse a Cue Sheet file into a [Playlist]
|
5
|
+
# @param input any object that responds to #each_line
|
6
|
+
# (either a String or a IO object)
|
7
|
+
# @return [Playlist] a new Playlist object
|
8
|
+
def parse(input)
|
9
|
+
Playlist.new do |playlist|
|
10
|
+
track = nil
|
11
|
+
input.each_line do |line|
|
12
|
+
if line =~ /^\s*REM\s/
|
13
|
+
next
|
14
|
+
elsif line =~ /^\s*TRACK (\d+) AUDIO/
|
15
|
+
track = Playlist::Track.new(
|
16
|
+
:track_number => Regexp.last_match(1).to_i
|
17
|
+
)
|
18
|
+
playlist.add_track(track)
|
19
|
+
elsif !track.nil?
|
20
|
+
parse_track_line(track, line.strip)
|
21
|
+
else
|
22
|
+
parse_playlist_line(playlist, line.strip)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Generate a Cue Sheet from a Playlist
|
29
|
+
# @param playlist [Playlist] the playlist
|
30
|
+
# @return [String] the playlist in Cue Sheet format
|
31
|
+
def generate(playlist)
|
32
|
+
text = ''
|
33
|
+
playlist.calculate_start_times
|
34
|
+
text += generate_line(0, 'TITLE', playlist.title)
|
35
|
+
text += generate_line(
|
36
|
+
0, 'FILE', format_filename(playlist.media_location), false
|
37
|
+
)
|
38
|
+
playlist.tracks.each_with_index do |track, index|
|
39
|
+
text += generate_track(track, index + 1)
|
40
|
+
end
|
41
|
+
text
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def parse_track_line(track, line)
|
47
|
+
if line =~ /^\s*TITLE \"?(.+?)\"?$/
|
48
|
+
track.title = Regexp.last_match(1)
|
49
|
+
elsif line =~ /^\s*PERFORMER \"?(.+?)\"?$/
|
50
|
+
track.performer = Regexp.last_match(1)
|
51
|
+
elsif line =~ /^\s*ISRC \"?(\w+?)\"?$/
|
52
|
+
track.isrc = Regexp.last_match(1)
|
53
|
+
elsif line =~ /^\s*INDEX /
|
54
|
+
track.start_time = parse_time(line)
|
55
|
+
else
|
56
|
+
warn "Unknown command: #{line}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_playlist_line(playlist, line)
|
61
|
+
if line =~ /^\s*TITLE \"?(.+?)\"?$/
|
62
|
+
playlist.title = Regexp.last_match(1)
|
63
|
+
elsif line =~ /^\s*FILE \"?(.+?)\" (\w+)?$/
|
64
|
+
playlist.media_location = Regexp.last_match(1)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def generate_track(track, track_num)
|
69
|
+
text = ''
|
70
|
+
text += generate_line(1, 'TRACK', format('%2.2d AUDIO', track_num), false)
|
71
|
+
text += generate_line(2, 'TITLE', track.title)
|
72
|
+
text += generate_line(2, 'PERFORMER', track.performer)
|
73
|
+
text += generate_line(2, 'ISRC', track.isrc)
|
74
|
+
text += generate_line(2, 'INDEX 01', format_time(track.start_time), false)
|
75
|
+
text
|
76
|
+
end
|
77
|
+
|
78
|
+
def generate_line(depth, name, value, escape = true)
|
79
|
+
if value.nil?
|
80
|
+
''
|
81
|
+
else
|
82
|
+
space = ' ' * depth
|
83
|
+
if escape
|
84
|
+
escaped = value.tr('"', "'")
|
85
|
+
"#{space}#{name} \"#{escaped}\"\n"
|
86
|
+
else
|
87
|
+
"#{space}#{name} #{value}\n"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse_time(timestamp)
|
93
|
+
if timestamp =~ /INDEX (\d+) (\d+):(\d+):(\d+)/
|
94
|
+
if Regexp.last_match(1).to_i == 1
|
95
|
+
mins = Regexp.last_match(2).to_f
|
96
|
+
secs = Regexp.last_match(3).to_f
|
97
|
+
frames = Regexp.last_match(4).to_f
|
98
|
+
(mins * 60) + secs + (frames / 75)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def format_time(duration)
|
104
|
+
duration = 0 if duration.nil?
|
105
|
+
mins = (duration / 60).floor
|
106
|
+
secs = (duration % 60).floor
|
107
|
+
frames = ((duration - duration.floor) * 75).round
|
108
|
+
format('%2.2d:%2.2d:%2.2d', mins, secs, frames)
|
109
|
+
end
|
110
|
+
|
111
|
+
def format_filename(location)
|
112
|
+
filename = File.basename(location)
|
113
|
+
fmt = File.extname(filename)[1..-1].upcase
|
114
|
+
"\"#{filename}\" #{fmt}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/lib/playlist/track.rb
CHANGED
@@ -11,6 +11,11 @@ class Playlist::Track
|
|
11
11
|
# @return [String]
|
12
12
|
attr_accessor :album
|
13
13
|
|
14
|
+
# The catalogue number of the album that the track came from
|
15
|
+
# Also known as the UPC/EAN code
|
16
|
+
# @return [String]
|
17
|
+
attr_accessor :catalogue_number
|
18
|
+
|
14
19
|
# The number of the track on the album it came from
|
15
20
|
# @return [Integer]
|
16
21
|
attr_accessor :track_number
|
@@ -37,6 +42,11 @@ class Playlist::Track
|
|
37
42
|
# @return [Integer, Float]
|
38
43
|
attr_reader :duration
|
39
44
|
|
45
|
+
# Get a hash of identifier for this Track
|
46
|
+
# Identifiers can either be Strings or URIs
|
47
|
+
# @return [Hash] an hash of identifiers
|
48
|
+
attr_reader :identifiers
|
49
|
+
|
40
50
|
# Get the array of the contributors to this Track
|
41
51
|
# @return [Array<Contributor>] an array of tracks in the playlist
|
42
52
|
attr_reader :contributors
|
@@ -45,6 +55,7 @@ class Playlist::Track
|
|
45
55
|
# @param attr [Hash] a hash of attibute values to set
|
46
56
|
def initialize(attr = {})
|
47
57
|
@contributors = []
|
58
|
+
@identifiers = {}
|
48
59
|
attr.each_pair do |key, value|
|
49
60
|
send("#{key}=", value)
|
50
61
|
end
|
@@ -52,6 +63,18 @@ class Playlist::Track
|
|
52
63
|
yield(self) if block_given?
|
53
64
|
end
|
54
65
|
|
66
|
+
# Get the International Standard Recording Code for this track
|
67
|
+
# @return [String] the ISRC for the track
|
68
|
+
def isrc
|
69
|
+
@identifiers[:isrc]
|
70
|
+
end
|
71
|
+
|
72
|
+
# Set the International Standard Recording Code for this track
|
73
|
+
# @param isrc [String] the ISRC for the track
|
74
|
+
def isrc=(isrc)
|
75
|
+
@identifiers[:isrc] = isrc
|
76
|
+
end
|
77
|
+
|
55
78
|
# Get a concatinated list of contributors names with no role
|
56
79
|
# @return [String]
|
57
80
|
def creator
|
@@ -81,6 +104,32 @@ class Playlist::Track
|
|
81
104
|
end
|
82
105
|
alias artist= performer=
|
83
106
|
|
107
|
+
# Get a conactinated list of composers for this track
|
108
|
+
# @return [String] the name of the composer or nil
|
109
|
+
def composer
|
110
|
+
contributor_names(:composer)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Set the name of the composer for the track
|
114
|
+
# Removes any existing composers
|
115
|
+
# @param name [String] the name the composer
|
116
|
+
def composer=(name)
|
117
|
+
replace_contributor(:composer, name)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Get a conactinated list of arrangers for this track
|
121
|
+
# @return [String] the name of the arranger or nil
|
122
|
+
def arranger
|
123
|
+
contributor_names(:arranger)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Set the name of the arranger for the track
|
127
|
+
# Removes any existing arrangers
|
128
|
+
# @param name [String] the name the arranger
|
129
|
+
def arranger=(name)
|
130
|
+
replace_contributor(:arranger, name)
|
131
|
+
end
|
132
|
+
|
84
133
|
# Set the duration of the track
|
85
134
|
# If the duration is 0 or -1, then the duration is set to nil
|
86
135
|
# @param seconds [Numeric] the duration of the track in seconds
|
data/lib/playlist/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: playlist
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nicholas Humfrey
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-07-
|
11
|
+
date: 2018-07-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -140,7 +140,7 @@ files:
|
|
140
140
|
- lib/playlist/contributor.rb
|
141
141
|
- lib/playlist/ext/content_at.rb
|
142
142
|
- lib/playlist/format.rb
|
143
|
-
- lib/playlist/format/
|
143
|
+
- lib/playlist/format/cue.rb
|
144
144
|
- lib/playlist/format/m3u.rb
|
145
145
|
- lib/playlist/format/simple_text.rb
|
146
146
|
- lib/playlist/format/xspf.rb
|
data/lib/playlist/format/dira.rb
DELETED
@@ -1,57 +0,0 @@
|
|
1
|
-
require 'nokogiri'
|
2
|
-
|
3
|
-
unless Nokogiri::XML::Node.respond_to?(:content_at)
|
4
|
-
require 'playlist/ext/content_at.rb'
|
5
|
-
end
|
6
|
-
|
7
|
-
# Module to parse Scisys dira XML genealogy files
|
8
|
-
module Playlist::Format::Dira
|
9
|
-
class << self
|
10
|
-
# Parse a Dira XML document into a new Playlist object
|
11
|
-
# @param input [String, IO] the source of the Dira XML
|
12
|
-
# @return [Playlist] a new Playlist object
|
13
|
-
def parse(input)
|
14
|
-
Playlist.new do |playlist|
|
15
|
-
doc = Nokogiri::XML(input)
|
16
|
-
playlist.title = doc.content_at(
|
17
|
-
'//TAKE/GENERIC/GENE_MULTIMEDIA_TITLE'
|
18
|
-
) || doc.content_at(
|
19
|
-
'//TAKE/GENERIC/GENE_TITLE'
|
20
|
-
)
|
21
|
-
doc.xpath('//TAKE/GENEALOGY/GENEALOGY_ITEM').each do |item|
|
22
|
-
playlist.tracks << parse_genealogy_item(item)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
protected
|
28
|
-
|
29
|
-
# Parse a single Genealogy Item from a Dira XML document
|
30
|
-
def parse_genealogy_item(doc)
|
31
|
-
Playlist::Track.new do |track|
|
32
|
-
track.title = doc.content_at('./CLIP/CLIP_INFO/GENE_TITLE')
|
33
|
-
track.album = doc.content_at('./CLIP/CLIP_INFO/GENE_ALBUM')
|
34
|
-
track.track_number = doc.content_at('./CLIP/CLIP_INFO/GENE_TRACK')
|
35
|
-
track.side = doc.content_at('./CLIP/CLIP_INFO/GENE_SIDE')
|
36
|
-
track.record_label = doc.content_at('./CLIP/CLIP_INFO/GENE_LABEL')
|
37
|
-
track.publisher = doc.content_at('./CLIP/CLIP_INFO/GENE_PUBLISHER')
|
38
|
-
if (duration = doc.content_at('./GENY_CLIP_LENGTH'))
|
39
|
-
track.duration = duration.to_f / 1000
|
40
|
-
end
|
41
|
-
doc.xpath('./CLIP/CLIP_PERSONS/PERSON').each do |person|
|
42
|
-
track.contributors << parse_person(person)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# Parse a single Person from a dira XML document
|
48
|
-
def parse_person(doc)
|
49
|
-
Playlist::Contributor.new do |c|
|
50
|
-
c.name = doc.content_at('PERSON_NAME')
|
51
|
-
if doc.content_at('PMAP_FUNC') =~ /^PERSON_FUNC\$(.+)\#(.*)$/
|
52
|
-
c.role = Regexp.last_match(1).downcase.to_sym
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|