playlist 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|