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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b3afe56ad81f3b8f00d9132b64f654b14c60c977
4
- data.tar.gz: 0d7fd4cf379196ecbac561a8471d8011bb7d3682
3
+ metadata.gz: e0abdf1a10cd890a9d446a6617ed5bf12856dba0
4
+ data.tar.gz: 0719e57569fed6a8798e58db21eb50dd6c7dfc34
5
5
  SHA512:
6
- metadata.gz: 28a5a2a72c16ffd5f19fbd97adf9317bcdb026f7e537623cafdb160ef88f1ee08cd427dc7b00bf6a81b3aa0d38f235f75febb6137479d15a75a3bc4372c2f938
7
- data.tar.gz: 1f5c2c2abd8874c200005a2b2a9e70a126683dc0a7d9890166a3c67b59ca0e5e7c50701619438450339d3fd0b9ad1cfa786b2b185fa1591e0e6ea791639941b0
6
+ metadata.gz: 44aa5aff176b61c1ad739765882e4c7a7dde57f828a9f4eee95bf19112362d8bcb4b55fe24145e0b435e48f56746ff6bba83086bf810862ffe584f253821cbdc
7
+ data.tar.gz: e71116d7a8906db2cea15f117a4de683945e47b7fb285a36c8043635491c94979653be85adb0b90e4fd947d7fe6626baf4823fd128e32a58414a22ee78836c6b
data/README.md CHANGED
@@ -9,7 +9,6 @@ It supports parsing and generating playlists in the following formats:
9
9
  * M3U
10
10
  * XSPF
11
11
  * A simple human readable format
12
- * SciSys dira XML
13
12
 
14
13
  ## Installation
15
14
 
@@ -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'
@@ -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)
@@ -1,6 +1,6 @@
1
1
  # Base module for the various playlist formats
2
2
  module Playlist::Format
3
- autoload :Dira, 'playlist/format/dira'
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
@@ -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
@@ -1,4 +1,4 @@
1
1
  class Playlist
2
2
  # The version number of the Playlist Ruby gem
3
- VERSION = '0.1.3'
3
+ VERSION = '0.2.0'
4
4
  end
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.1.3
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-16 00:00:00.000000000 Z
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/dira.rb
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
@@ -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