media-organizer 0.1.5 → 0.1.6

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2b335d2088f9101cc6029772adee22c6700a5172
4
+ data.tar.gz: 04066471b4bc4c5a435ad382573caff3de4268a7
5
+ SHA512:
6
+ metadata.gz: f466fc9895d1ecfc1e42f66a194d5123210876ee04f7846c100d4a8d23f4bb65fc2e1d3160e1caa05642b57f8a715653f6710d56d8d1b2c3d35d4d92e815a5ef
7
+ data.tar.gz: b6f2cb8be7177a70e0304bd64240f03541cc886bd9ef44f8b57333f9785f616af3d95c23a8a0cef12da3991e6330016ffb08a6102671f6f2c964333342154034
@@ -1,115 +1,144 @@
1
1
  #
2
- #filescanner.rb: defines the class Filescanner, which scans directory trees for media files
2
+ # filescanner.rb: defines the class Filescanner, which scans directory trees for media files
3
3
  #
4
4
 
5
5
  require 'scrapers/image.rb'
6
6
  require 'scrapers/music.rb'
7
7
 
8
-
9
8
  module MediaOrganizer
10
-
11
-
12
- #FileScanner: scans a file system for images and/or music files, providing a list of file URIs.
13
- #Output of FileScanner.open() returns a list of paths that can then be consumed by MediaOrganizer::Renamer.
14
- #
15
- #===Example
16
- #
17
- #Filescanner.open("/path/to/files/", {:image => true, :music => true})
18
- #
19
- #This will return a list of all music and image files contained in /path/to/files/ and it's
20
9
  class Filescanner
21
- class FileNotValidError < StandardError ; end
22
- class FileNotFoundError < StandardError ; end
23
-
10
+ include Image
11
+ include Music
24
12
 
25
13
  attr_reader :root_nodes
26
14
  attr_accessor :source_list
27
15
 
28
-
29
- def initialize()
16
+ def initialize
30
17
  @root_nodes = []
31
18
  @source_list = []
32
19
  end
33
20
 
34
21
  #
35
- #Filescanner.open(String, {}): scans directory tree for media files, starting at String specified in first argument.
22
+ # Filescanner.open(String, {}): scans directory tree for media files,
23
+ # starting at String specified in first argument.
36
24
  #
37
- #===Inputs
38
- # *(1) String: String containing the URI of the top of the directory tree to scan
25
+ #==Inputs
26
+ #===Required
27
+ # (1) String: String containing the URI of the top of the directory tree to scan
39
28
  #
40
- # *2: Optional Arguments Hash:
41
- # *:mode => (:single) -- if set to :single, only the given URI will be scanned. Subdirectories will be ignored.
42
- # *:music => (true, false) -- if true, music files will be included in the scan. Set to false to exclude music files. Defaults to true
43
- # *:image => (true, false) -- if true, image files will be included in the scan. Set to false to exclude image files. Defaults to true
29
+ #===Optional
30
+ # (2) Arguments Hash:
31
+ # *:mode => (:single) -- if set to :single, only the given URI will be scanned.
32
+ # Subdirectories will be ignored.
33
+ # *:music => (true, false) -- if true, music files will be included in the scan.
34
+ # Set to false to exclude music files. Defaults to true
35
+ # *:image => (true, false) -- if true, image files will be included in the scan.
36
+ # Set to false to exclude image files. Defaults to true
44
37
  #
45
- #===Outputs
46
- #Returns array of strings, where each string is a file URI for a music or image file.
38
+ #==Outputs
39
+ # Returns array of strings, where each string is a file URI for a music or image file.
47
40
  #
48
41
  #
49
- #===Example
50
- #Filescanner.open("/absolute/path/for/top/of/directory/tree")
42
+ #==Usage Example
43
+ # Filescanner.open("/absolute/path/for/top/of/directory/tree")
51
44
  #
52
- def open(uri = "", args = {})
53
- unless !uri.nil? && uri.is_a?(String) && (File.directory?(uri) || File.exists?(uri))
54
- raise FileNotFoundError, "Directory given (#{uri}) could not be accessed."
55
- end
56
-
57
- include_images = true unless args[:image] == false
58
- include_music = true unless args[:music] == false
59
- files = []
60
- if args[:mode] == :single
61
- files = Dir.glob("#{uri}/*")
62
- else
63
- files = Dir.glob("#{uri}/**/*")
64
- end
65
-
66
- #add all files found to @source_list, if they are music files
45
+ def open(uri = '', args = {})
46
+ include_images = true unless args[:image] == false
47
+ include_music = true unless args[:music] == false
48
+
49
+ files = load_files(uri, args[:mode])
67
50
  files.each do |f|
68
- if (Music.is_music?(f) && include_music) || (Image.is_image?(f) && include_images)
51
+ if (Music.music?(f) && include_music) || (Image.image?(f) && include_images)
69
52
  @source_list << f
70
53
  end
71
54
  end
72
55
 
73
56
  return @source_list
74
57
 
75
- rescue FileNotFoundError => e
76
- puts e.message
77
- puts e.backtrace.inspect
58
+ rescue StandardError => e
59
+ puts "Warning: #{e.message}"
78
60
  return false
79
61
  end
80
62
 
81
- #Alternative run mode. Add multiple "root" directories to scan at once
82
- def addRoot(dir_uri)
83
- unless !dir_uri.nil? && dir_uri.is_a?(String) && File.directory?(dir_uri)
84
- raise FileNotFoundError, "Directory given (#{dir_uri}) could not be accessed."
85
- end
86
- @root_nodes << dir_uri
87
- rescue FileNotFoundError => e
88
- puts e.message
89
- puts e.backtrace.inspect
63
+ # alternative run mode. Add multiple "root" directories to scan at once
64
+ def add_root(dir_uri)
65
+ validate_uri(dir_uri)
66
+ @root_nodes << dir_uri
67
+ rescue StandardError => e
68
+ puts "Warning: #{e.message}"
90
69
  return false
91
70
  end
92
71
 
93
- #Filescanner:multiscan(): scans multiple directories added to @root_nodes using the addRoot() method.
72
+ # <<(): synonym for add_root(). Also a deformed emoticon.
73
+ # def << (dir_uri) add_root(dir_uri) end
74
+
75
+ #
76
+ # multiscan(): scans multiple directories added to @root_nodes using the add_root() method.
94
77
  #
95
- #===Inputs
78
+ #==Inputs
79
+ #===Required
80
+ # none
96
81
  #
97
- # @param [Hash] args Optional Arguments Hash:
98
- # @option args [Symbol] :mode sets the mode to :single or :multiple
99
- # @option args [Boolean] :music => (true, false) -- if true, music files will be included in the scan. Set to false to exclude music files. Defaults to true
100
- # @option args [Boolean] :image => (true, false) -- if true, image files will be included in the scan. Set to false to exclude image files. Defaults to true
82
+ #===Optional
83
+ # (1) Arguments Hash:
84
+ # *:mode => (:single, :multiple)
85
+ # *:music => (true, false) -- if true, music files will be included in the scan.
86
+ # Set to false to exclude music files. Defaults to true
87
+ # *:image => (true, false) -- if true, image files will be included in the scan.
88
+ # Set to false to exclude image files. Defaults to true
101
89
  #
102
- #===Outputs
103
- # @return [Array<String>] Array of strings, where each string is a file URI for a music or image file.
90
+ #==Outputs
91
+ # Array of strings, where each string is a file URI for a music or image file.
104
92
  #
105
93
  def multiscan(args = {})
106
94
  @root_nodes.each do |uri|
107
95
  open(uri, args)
108
96
  end
109
- return @source_list
97
+ @source_list
110
98
  end
111
-
112
- end
113
99
 
114
- end
100
+ private
115
101
 
102
+ # validate_uri(): support method for validating a file system URI.
103
+ #
104
+ #==Inputs
105
+ #===Required
106
+ # (1) String: String containing the URI to validate.
107
+ #
108
+ #===Optional
109
+ # none
110
+ #==Outputs
111
+ # Returns true if the URI is valid. Raises a StandardError exception if it is not.
112
+ #
113
+ def validate_uri(uri)
114
+ unless !uri.nil? && uri.is_a?(String) && (File.directory?(uri) || File.exist?(uri))
115
+ raise StandardError, "Directory given (#{uri}) could not be accessed."
116
+ end
117
+ true
118
+ end
119
+
120
+ #
121
+ # load_files(): given a URI and optional mode, scans and loads every file in
122
+ # the specifid folder or tree.
123
+ #
124
+ #==Inputs
125
+ #===Required
126
+ # (1) String: String containing the URI to validate.
127
+ #
128
+ #===Optional
129
+ # (1) Arguments Hash:
130
+ # *:mode => (:single, :multiple) - :single loads a folder, :multiple loads a full tree.
131
+ #
132
+ #==Outputs
133
+ # Array of strings, where each string is a file URI for a music or image file.
134
+ #
135
+ def load_files(uri, mode)
136
+ validate_uri(uri)
137
+ if !mode.nil? && mode == :single
138
+ Dir.glob("#{uri}/*")
139
+ else
140
+ Dir.glob("#{uri}/**/*")
141
+ end
142
+ end
143
+ end
144
+ end
@@ -1,15 +1,8 @@
1
- #media-renamer.rb
1
+ # media-renamer.rb
2
2
 
3
3
  require 'renamer.rb'
4
4
  require 'filescanner.rb'
5
5
 
6
- #MediaOrganizer: namespace container for MediaOrganizer sub-classes:
7
- # *Renamer
8
- # *Filescanner
9
- # *Image
10
- # *Music
11
6
  module MediaOrganizer
12
- VERSION = "0.1.4"
13
-
7
+ VERSION = '0.1.5'.freeze
14
8
  end
15
-
@@ -0,0 +1,19 @@
1
+ # metadata.rb: contains definition of the Metadata module, which provides
2
+ # a set of methods for analyzing metadata across multiple files, such as:
3
+ # *Metadata.completeness(arr, fields): for each metadata field provided in the "fields" argument, returns a completeness score.
4
+ # **E.g. {date_time: .95} means 95% of the files in arr have data in the "date_time" field
5
+ # *Metadata.fields(arr): returns the full list of fields available, aggregated across all files in arr
6
+
7
+ require 'scrapers/music.rb'
8
+ require 'scrapers/image.rb'
9
+
10
+ module Metadata
11
+ def self.completeness(arr = [], fields = [])
12
+ fields_count = {}
13
+ fields.each do |i|
14
+ fields_count[i] = 0
15
+ end
16
+ arr.each do |file|
17
+ end
18
+ end
19
+ end
@@ -1,209 +1,193 @@
1
- #renamer.rb: main codebase for the media-renamer gem.
2
- #Currently configured to only rename JPG and TIF files (using EXIFR to extract metadata)
3
- #next major release will include support
1
+ # renamer.rb: main codebase for the media-renamer gem.
2
+ # Currently configured to only rename JPG and TIF files (using EXIFR to extract metadata)
3
+ # next major release will include support
4
4
  require 'scrapers/image.rb'
5
5
  require 'scrapers/music.rb'
6
6
 
7
7
  module MediaOrganizer
8
-
9
- #Renamer: primary class to use for renaming files. Allows renaming of a given list of files to a user-defined scheme based on each file's metadata.
10
- #
11
- #===Key Methods
12
- #* setNamingScheme()
13
- #* generateRenameList()
14
- #* overwrite()
15
- #
16
- #===Example Usage
17
- #
18
- # old_uris = ['./test/data/hs-2003-24-a-full_tif.tif']
19
- # scheme = ["Test-", :date_time]
20
- #
21
- # r = Renamer.new()
22
- #
23
- # r.setNamingScheme(scheme)
24
- #
25
- # new_uris = r.generateRenameList(old_uris)
26
- #
27
- # r.overwrite(new_uris) #new filename: "./test/data/Test-2003-09-03 12_52_43 -0400.tif")
28
- #
29
- class Renamer
30
- DISALLOWED_CHARACTERS = /[\\:\?\*<>\|"\/]/ #Characters that are not allowed in file names by many file systems. Replaced with @subchar character.
31
- class RenameFailedError < StandardError ; end
32
- class FileNotValidError < StandardError ; end
33
- class InvalidArgumentError < StandardError ; end
34
- class UnsupportedFileTypeError < StandardError ; end
35
- class FileNotFoundError < StandardError ; end
36
-
37
-
38
- attr_accessor :naming_scheme #Array of strings and literals used to construct filenames. Set thruough setNamingScheme as opposed to typical/default accessor.
39
- attr_accessor :subchar #Character with which to substitute disallowed characters
40
-
41
- def initialize(args = {})
42
- @naming_scheme = ["Renamed-default-"]
43
- @subchar = "_"
44
- end
45
-
46
- # Renamer.setNamingScheme(): sets the naming scheme for the generateRenameList method.
47
- #
48
- # @param [Array<String, Symbol>] arr contains strings and symbols representing the naming scheme.
49
- #
50
- # @return [void] instance variable @naming_scheme is set based upon input
51
- #
52
- # @example Set the naming scheme to a format like "Vacation_Photos_2014_05_22.png" based on the file's date_taken metadata field.
53
- # setNamingScheme(["Vacation_Photos_", :date_taken]).
54
- #
55
- def setNamingScheme(arr = [])
56
- @naming_scheme = setScheme(arr)
57
- end
58
-
59
- #Renamer.generateRenameList(): Creates a hash mapping the original filenames to the new (renamed) filenames
60
- #
61
- # @param [Array<String>] uri_list contains list of URIs (as full AKA absolute paths)
62
- # @param [Hash] args hash of options.
63
- # @option args [Array<String, Symbol>] :scheme - array of strings and symbols specifying file naming convention
64
- #
65
- # @return [Hash] filename_pairs - mapping between old filenames and new filenames
66
- #Hash of "file name pairs." old_file => new_file
67
- def generateRenameList(uri_list = [], args = {})
68
- if args[:scheme] != nil && args[:scheme].is_a?(Array) && !args[:scheme].empty?
69
- scheme = setScheme(args[:scheme])
70
- else
71
- scheme = @naming_scheme
72
- end
73
- unless !uri_list.nil? && uri_list.is_a?(Array)
74
- raise InvalidArgumentError
75
- end
76
-
77
- filename_pairs = {}
78
- uri_list.each do |i|
79
- new_string = handleFile(i, scheme)
80
- #If this is a valid file path, add it to the filename_pairs
81
- #puts "New file rename added: #{new_string}"
82
- if new_string != nil && new_string != ""
83
- filename_pairs[i] = new_string
84
- end
85
- end
86
-
87
- return filename_pairs
88
-
89
- rescue InvalidArgumentError => arg_e
90
- puts arg_e
91
- puts "Invalid arguments provided. Expected: uri_list = [], args = {}"
92
- puts arg_e.backtrace.inspect
93
- rescue => e
94
- puts e
95
- puts e.message
96
- puts e.backtrace.inspect
97
- end
98
-
99
- #Renamer.overwrite(): Writes new file names based upon mapping provided in hash argument. NOTE: this will create changes to file names!
100
- #
101
- #===Inputs
102
- # 1. Hash containing mappings between old filenames (full URI) and new filenames (full URI). Example: {"/path/to/oldfile.jpg" => "/path/to/newfile.jpg"}
103
- #
104
- #===Outputs
105
- #none (file names are overwritten)
106
- def overwrite(renames_hash = {})
107
- renames_hash.each do |old_name, new_name|
108
- begin
109
- #error/integrity checking on old_name and new_name
110
- raise FileNotValidError, "Could not access specified source file: #{i}." unless old_name.is_a?(String) && File.exists?(old_name)
111
- raise FileNotValidError, "New file name provided is not a string" unless new_name.is_a?(String)
112
-
113
- #puts (File.dirname(File.absolute_path(old_name)) + "/" + new_name) #Comment this line out unless testing
114
- File.rename(File.absolute_path(old_name),File.dirname(File.absolute_path(old_name)) + "/" + new_name)
115
-
116
- #check that renamed file exists - Commented out because this currently does not work.
117
- #unless new_name.is_a?(String) && File.exists?(new_name)
118
- # raise RenameFailedError, "Could not successfuly rename file: #{old_name} => #{new_name}. Invalid URI or file does not exist."
119
- #end
120
- rescue => e
121
- puts "Ignoring rename for #{old_name} => #{new_name}"
122
- puts e
123
- puts e.backtrace.inspect
124
- end
125
- end
126
- end
127
-
128
- #Routes metadata scrape based on file type (currently relies on extension - future version should use MIME)
129
- #currently assumes file was checked for validity in calling code.
130
- #
131
- #===Inputs
132
- #String containing full file URI (path and filename)
133
- #
134
- #===Outputs
135
- #Returns hash of metadata for file, or nil if none/error.
136
- def getFileMetadata(file)
137
-
138
- #LOAD EXIF DATA
139
- case File.extname(file).downcase
140
- when '.jpg'
141
- Image::getJpegData(file)
142
- when '.tif'
143
- Image::getTiffData(file)
144
- when '.mp3' , '.wav' , '.flac' , '.aiff', '.ogg', '.m4a', '.asf'
145
- Music::getMusicData(file)
146
- else
147
- raise UnsupportedFileTypeError, "Error processing #{file}"
148
- end
149
- rescue UnsupportedFileTypeError => e
150
- puts "Could not process file: Extension #{File.extname(file)} is not supported."
151
- puts e.backtrace.inspect
152
- end
153
-
154
-
155
- private
156
- def handleFile(file, scheme)
157
- #Check file is real
158
- unless file.is_a?(String) && File.exists?(file)
159
- raise FileNotValidError, "Could not access specified file file: #{file}."
160
- end
161
- #convert URI (i) to absolute path
162
-
163
- #get metadata hash for this file (i)
164
- metadata = getFileMetadata(File.absolute_path(file))
165
- #build URI string
166
- new_string = ""
167
- scheme.each do |j|
168
- if j.is_a?(String) then new_string += j
169
- elsif j.is_a?(Symbol)
170
- begin
171
- raise EmptyMetadataError unless metadata[j] != nil
172
- new_string += metadata[j].to_s
173
- rescue => e
174
- puts "Could not get string for metadata tag provided in scheme: #{j} for file #{file}."
175
- puts "Ignoring file #{file}"
176
- puts e.backtrace.inspect
177
- return nil
178
- end
179
- end
180
- end
181
- #puts "Found file metadata: #{metadata[:date_time]}"
182
- return subHazardousChars(new_string + File.extname(file))
183
- rescue FileNotValidError => e
184
- puts ("Ignoring file #{file}")
185
- puts e
186
- puts e.backtrace
187
- return nil
188
- rescue => e
189
- puts e.message
190
- puts e.backtrace.inspect
191
- return nil
192
- end
193
-
194
- def setScheme(input_arr = [])
195
- clean_scheme = []
196
- input_arr.each do |i|
197
- if i.is_a?(String) || i.is_a?(Symbol)
198
- clean_scheme << i
199
- end
200
- end
201
- return clean_scheme
202
- end
203
-
204
- def subHazardousChars(str = "")
205
- return str.gsub(DISALLOWED_CHARACTERS, @subchar)
206
- end
207
- end
208
-
8
+ class FileNotValidError < StandardError; end
9
+ class InvalidArgumentError < StandardError; end
10
+ class UnsupportedFileTypeError < StandardError; end
11
+ class RenameFailedError < StandardError; end
12
+
13
+ # Renamer: primary class to use for renaming files. Allows renaming of a given list of files to a user-defined scheme based on each file's metadata.
14
+ #
15
+ #===Key Methods
16
+ # *naming_scheme()
17
+ # *generate()
18
+ # *overwrite()
19
+ #
20
+ #===Example Usage
21
+ #
22
+ # old_uris = ['./test/data/hs-2003-24-a-full_tif.tif']
23
+ #
24
+ # scheme = ["Test-", :date_time]
25
+ #
26
+ # r = Renamer.new()
27
+ #
28
+ # r.set_naming_scheme(scheme)
29
+ #
30
+ # new_uris = r.generate(old_uris)
31
+ #
32
+ # r.overwrite(new_uris) #new filename: "./test/data/Test-2003-09-03 12_52_43 -0400.tif")
33
+ #
34
+ class Renamer
35
+ DISALLOWED_CHARACTERS = /[\\:\?\*<>\|"\/]/ # Characters that are not allowed in file names by many file systems. Replaced with @subchar character.
36
+
37
+ attr_reader :naming_scheme # Array of strings and literals used to construct filenames. Set thruough naming_scheme as opposed to typical/default accessor.
38
+ attr_accessor :subchar # Character with which to substitute disallowed characters
39
+
40
+ def initialize(_args = {})
41
+ @naming_scheme = ['Renamed-default-']
42
+ @subchar = '_'
43
+ end
44
+
45
+ # Renamer.naming_scheme(): sets the naming scheme for the generate method.
46
+ #
47
+ #===Inputs
48
+ # 1. Array containing strings and symbols.
49
+ #
50
+ #===Outputs
51
+ # None (sets instance variable @naming_scheme)
52
+ #
53
+ #===Example
54
+ # naming_scheme(["Vacation_Photos_", :date_taken]).
55
+ # This will rename files into a format like "Vacation_Photos_2014_05_22.png" based on the file's date_taken metadata field.
56
+ def set_naming_scheme(arr = [])
57
+ @naming_scheme = set_scheme(arr)
58
+ end
59
+
60
+ # Renamer.generate(): Creates a hash mapping the original filenames to the new (renamed) filenames
61
+ #
62
+ #===Inputs
63
+ # 1. List of URIs in the form of an array
64
+ # 2. Optional hash of arguments.
65
+ # *:scheme - array of strings and symbols specifying file naming convention
66
+ #
67
+ #===Outputs
68
+ # Hash of "file name pairs." old_file => new_file
69
+ def generate(uri_list = [], args = {})
70
+ scheme = if !args[:scheme].nil? && args[:scheme].is_a?(Array) && !args[:scheme].empty?
71
+ set_scheme(args[:scheme])
72
+ else
73
+ @naming_scheme
74
+ end
75
+ raise InvalidArgumentError unless !uri_list.nil? && uri_list.is_a?(Array)
76
+
77
+ filename_pairs = {}
78
+ uri_list.each do |i|
79
+ new_string = handle_file(i, scheme)
80
+ # If this is a valid file path, add it to the filename_pairs
81
+ # puts "New file rename added: #{new_string}"
82
+ filename_pairs[i] = new_string if !new_string.nil? && new_string != ''
83
+ end
84
+
85
+ return filename_pairs
86
+
87
+ rescue InvalidArgumentError => arg_e
88
+ puts arg_e
89
+ puts 'Invalid arguments provided. Expected: uri_list = [], args = {}'
90
+ rescue => e
91
+ puts e.message
92
+ puts e.backtrace.inspect
93
+ end
94
+
95
+ # Renamer.overwrite(): Writes new file names based upon mapping provided in hash argument. NOTE: this will create changes to file names!
96
+ #
97
+ #===Inputs
98
+ # 1. Hash containing mappings between old filenames (full URI) and new filenames (full URI). Example: {"/path/to/oldfile.jpg" => "/path/to/newfile.jpg"}
99
+ #
100
+ #===Outputs
101
+ # none (file names are overwritten)
102
+ def overwrite(renames_hash = {})
103
+ renames_hash.each do |old_name, new_name|
104
+ begin
105
+ # error/integrity checking on old_name and new_name
106
+ raise FileNotValidError, "Could not access specified source file: #{i}." unless old_name.is_a?(String) && File.exist?(old_name)
107
+ raise FileNotValidError, 'New file name provided is not a string' unless new_name.is_a?(String)
108
+
109
+ # puts (File.dirname(File.absolute_path(old_name)) + "/" + new_name) #Comment this line out unless testing
110
+ File.rename(File.absolute_path(old_name), File.dirname(File.absolute_path(old_name)) + '/' + new_name)
111
+
112
+ # check that renamed File.exist? - Commented out because this currently does not work.
113
+ # unless new_name.is_a?(String) && File.exist?(new_name)
114
+ # raise RenameFailedError, "Could not successfuly rename file: #{old_name} => #{new_name}. Invalid URI or file does not exist."
115
+ # end
116
+ rescue => e
117
+ puts "Ignoring rename for #{old_name} => #{new_name}"
118
+ end
119
+ end
120
+ end
121
+
122
+ # Routes metadata scrape based on file type (currently relies on extension - future version should use MIME)
123
+ # currently assumes file was checked for validity in calling code.
124
+ #
125
+ #===Inputs
126
+ # String containing full file URI (path and filename)
127
+ #
128
+ #===Outputs
129
+ # Returns hash of metadata for file, or nil if none/error.
130
+ def get_metadata(file)
131
+ # LOAD EXIF DATA
132
+ case File.extname(file).downcase
133
+ when '.jpg'
134
+ Image.get_jpeg_data(file)
135
+ when '.tif'
136
+ Image.get_tiff_data(file)
137
+ when '.mp3', '.wav', '.flac', '.aiff', '.ogg', '.m4a', '.asf'
138
+ Music.get_music_data(file)
139
+ else
140
+ raise UnsupportedFileTypeError, "Error processing #{file}"
141
+ end
142
+ rescue UnsupportedFileTypeError => e
143
+ puts "Could not process file: Extension #{File.extname(file)} is not supported."
144
+ puts e.backtrace.inspect
145
+ end
146
+
147
+ private
148
+
149
+ def handle_file(file, scheme)
150
+ # Check file is real
151
+ unless file.is_a?(String) && File.exist?(file)
152
+ raise FileNotValidError, "Could not access specified file file: #{file}."
153
+ end
154
+ metadata = get_metadata(File.absolute_path(file))
155
+ new_string = ''
156
+ scheme.each do |j|
157
+ if j.is_a?(String) then new_string += j
158
+ elsif j.is_a?(Symbol)
159
+ begin
160
+ raise EmptyMetadataError if metadata[j].nil?
161
+ new_string += metadata[j].to_s
162
+ rescue => e
163
+ puts "Could not get string for metadata tag provided in scheme: #{j} for file #{file}."
164
+ puts "Ignoring file #{file}"
165
+ return nil
166
+ end
167
+ end
168
+ end
169
+ return sub_hazardous_chars(new_string + File.extname(file))
170
+
171
+ rescue FileNotValidError => e
172
+ puts "Ignoring file #{file}"
173
+ puts e
174
+ return nil
175
+ rescue => e
176
+ puts e.message
177
+ puts e.backtrace.inspect
178
+ return nil
179
+ end
180
+
181
+ def set_scheme(input_arr = [])
182
+ clean_scheme = []
183
+ input_arr.each do |i|
184
+ clean_scheme << i if i.is_a?(String) || i.is_a?(Symbol)
185
+ end
186
+ clean_scheme
187
+ end
188
+
189
+ def sub_hazardous_chars(str = '')
190
+ str.gsub(DISALLOWED_CHARACTERS, @subchar)
191
+ end
192
+ end
209
193
  end
@@ -1,46 +1,45 @@
1
+ #
2
+ # image.rb: defines the class Image, which validates and loads
3
+ # image files, providing image metadata in a hash format.
4
+ #
5
+
1
6
  require 'exifr'
2
7
 
3
8
  module MediaOrganizer
4
-
5
- module Image
6
- SUPPORTED_FILETYPES = %w{.jpg .tif}
7
- class FileNotFoundError < StandardError ; end
8
- #
9
- # @param [String] file URI for the file from which to pull metadata
10
- #
11
- # @return [Hash] metadata fields associated with the file provided as input
12
- def Image.getJpegData(file)
13
- meta = EXIFR::JPEG.new(file)
14
- return meta.to_hash
15
- #!!! Rescue from common file-related and exifr-related errors here
16
- end
17
-
18
- def Image.getTiffData(file)
19
- meta = EXIFR::TIFF.new(file)
20
- return meta.to_hash
21
- #!!! Rescue from common file-related and exifr-related errors here
22
- end
23
-
24
- def Image.supported_filetypes
25
- return SUPPORTED_FILETYPES
26
- end
27
-
28
- def Image.is_image?(uri)
29
- unless !uri.nil? && uri.is_a?(String) && File.exists?(uri)
30
- raise FileNotFoundError, "Directory given (#{uri}) could not be accessed."
31
- end
32
-
33
- if SUPPORTED_FILETYPES.include?(File.extname(uri).downcase)
34
- return true
35
- else
36
- return false
37
- end
38
-
39
- rescue FileNotFoundError => e
40
- puts e.message
41
- puts e.backtrace.inspect
42
- return false
43
- end
44
-
45
- end
9
+ module Image
10
+ SUPPORTED_FILETYPES = %w(.jpg .tif).freeze
11
+
12
+ def self.get_jpeg_data(file)
13
+ meta = EXIFR::JPEG.new(file)
14
+ meta.to_hash
15
+ # !!! Rescue from common file-related and exifr-related errors here
16
+ end
17
+
18
+ def self.get_tiff_data(file)
19
+ meta = EXIFR::TIFF.new(file)
20
+ meta.to_hash
21
+ # !!! Rescue from common file-related and exifr-related errors here
22
+ end
23
+
24
+ def self.supported_filetypes
25
+ SUPPORTED_FILETYPES
26
+ end
27
+
28
+ def self.image?(uri)
29
+ unless !uri.nil? && uri.is_a?(String) && File.exist?(uri)
30
+ raise StandardError, "Directory given (#{uri}) could not be accessed."
31
+ end
32
+
33
+ if SUPPORTED_FILETYPES.include?(File.extname(uri).downcase)
34
+ return true
35
+ else
36
+ return false
37
+ end
38
+
39
+ rescue FileNotFoundError => e
40
+ puts e.message
41
+ puts e.backtrace.inspect
42
+ return false
43
+ end
44
+ end
46
45
  end
@@ -1,115 +1,112 @@
1
+ #
2
+ # music.rb: defines the scraper module Music, which validates and loads
3
+ # music files, providing image metadata in a hash format.
4
+ #
1
5
  require 'taglib'
2
6
 
3
7
  module MediaOrganizer
8
+ module Music
9
+ SUPPORTED_FILETYPES = %w(.mp3 .m4a .mp4 .flac .m4a .ogg .aiff .asf .wav).freeze
4
10
 
5
- module Music
6
- class FileNotFoundError < StandardError ; end
11
+ def self.get_music_data(file)
12
+ attributes = {}
13
+ TagLib::FileRef.open(file) do |fileref|
14
+ unless fileref.null?
15
+ # sign tags to local variables
16
+ tag = fileref.tag
17
+ properties = fileref.audio_properties
7
18
 
8
- SUPPORTED_FILETYPES = %w{.mp3 .m4a .mp4 .flac .m4a .ogg .aiff .asf .wav}
19
+ # load tags into attributes attribute
20
+ attributes[:title] = tag.title
21
+ attributes[:track] = tag.track
22
+ attributes[:genre] = tag.genre
23
+ attributes[:year] = tag.year
24
+ attributes[:album] = tag.album
25
+ attributes[:artist] = tag.artist
26
+ attributes[:comment] = tag.comment
9
27
 
10
- def Music.getMusicData(file)
11
- attributes = {}
12
- TagLib::FileRef.open(file) do |fileref|
13
- unless fileref.null?
14
- #sign tags to local variables
15
- tag = fileref.tag
16
- properties = fileref.audio_properties
17
-
18
- #load tags into attributes attribute
19
- attributes[:title] = tag.title
20
- attributes[:track] = tag.track
21
- attributes[:genre] = tag.genre
22
- attributes[:year] = tag.year
23
- attributes[:album] = tag.album
24
- attributes[:artist] = tag.artist
25
- attributes[:comment] = tag.comment
26
-
27
- attributes[:length] = properties.length
28
- attributes[:bitrate] = properties.bitrate
29
- attributes[:channels] = properties.channels
30
- attributes[:sample_rate] = properties.sample_rate
31
-
32
- end
33
- end
34
- return attributes
35
- end
28
+ attributes[:length] = properties.length
29
+ attributes[:bitrate] = properties.bitrate
30
+ attributes[:channels] = properties.channels
31
+ attributes[:sample_rate] = properties.sample_rate
36
32
 
37
- def Music.supported_filetypes
38
- reutrn SUPPORTED_FILETYPES
39
- end
33
+ end
34
+ end
35
+ attributes
36
+ end
40
37
 
41
- def Music.is_music?(uri)
42
- unless !uri.nil? && uri.is_a?(String) && File.exists?(uri)
43
- raise FileNotFoundError, "Directory given (#{uri}) could not be accessed."
44
- end
38
+ def self.supported_filetypes
39
+ reutrn SUPPORTED_FILETYPES
40
+ end
45
41
 
46
- if SUPPORTED_FILETYPES.include?(File.extname(uri).downcase)
47
- return true
48
- else
49
- return false
50
- end
42
+ def self.music?(uri)
43
+ unless !uri.nil? && uri.is_a?(String) && File.exist?(uri)
44
+ raise StandardError, "Directory given (#{uri}) could not be accessed."
45
+ end
51
46
 
52
- rescue FileNotFoundError => e
53
- puts e.message
54
- puts e.backtrace.inspect
55
- return false
56
- end
47
+ if SUPPORTED_FILETYPES.include?(File.extname(uri).downcase)
48
+ return true
49
+ else
50
+ return false
51
+ end
57
52
 
58
- #
59
- #availableMetadata(file, args): returns list of fields available as metadata for the given file.
60
- #
61
- #===Inputs
62
- # *(1) filepath: full/absolute URI of the file to be analyzed. Required input.
63
- # *(2) args: optional arguments passed as hash. Set ":include_null" to false to only return populated metadata fields.
64
- #
65
- def Music.availableMetadata(filepath = "", args = {})
66
- attrs = getMusicData(filepath)
53
+ rescue FileNotFoundError => e
54
+ puts e.message
55
+ puts e.backtrace.inspect
56
+ return false
57
+ end
67
58
 
68
- unless args[:include_null] == false
69
- attrs.each do |field, value|
70
- if value == nil || value == ""
71
- attrs.delete(field)
72
- end
73
- end
74
- end
75
- return attrs
76
- end
59
+ #
60
+ # availableMetadata(file, args): returns list of fields available as metadata for the given file.
61
+ #
62
+ #===Inputs
63
+ # *(1) filepath: full/absolute URI of the file to be analyzed. Required input.
64
+ # *(2) args: optional arguments passed as hash. Set ":include_null" to false to only return populated metadata fields.
65
+ #
66
+ def self.availableMetadata(filepath = '', args = {})
67
+ attrs = get_music_data(filepath)
77
68
 
78
- #
79
- #writeMetadata(file = "", meta = {}): returns list of fields available as metadata for the given file.
80
- #
81
- #===Inputs
82
- # *(1) filepath: full/absolute URI of the file to be analyzed. Required input.
83
- # *(2) meta: metadata to be written, passed as a hash in the format :metadata_field => metadata_value
84
- #
85
- #===Outputs
86
- #Returns true if the file was successfully saved. Note: true status does not necessarily indicate each field was successfully written.
87
- #
88
- #===Examples
89
- #Music.writeMetadata("/absolute/path/to/file.mp3", {:artist => "NewArtistName", :year => "2019"})
90
- #
91
- def Music.writeMetadata(filepath, meta = {})
92
- attributes = {}
93
- successflag = false
94
- TagLib::FileRef.open(filepath) do |fileref|
95
- unless fileref.null?
96
- #sign tags to local variables
97
- tag = fileref.tag
98
- properties = fileref.audio_properties
69
+ unless args[:include_null] == false
70
+ attrs.each do |field, value|
71
+ attrs.delete(field) if value.nil? || value == ''
72
+ end
73
+ end
74
+ attrs
75
+ end
99
76
 
100
- meta.each do |field, value|
101
- if tag.respond_to?(field)
102
- tag.send("#{field.to_s}=", value)
103
- elsif properties.respond_to?(field)
104
- properties.send("#{field.to_s}=", value)
105
- end
106
- end
107
- successflag = fileref.save
108
- end
109
- end
110
- return successflag
77
+ #
78
+ # writeMetadata(file = "", meta = {}): returns list of fields available as metadata for the given file.
79
+ #
80
+ #===Inputs
81
+ # *(1) filepath: full/absolute URI of the file to be analyzed. Required input.
82
+ # *(2) meta: metadata to be written, passed as a hash in the format :metadata_field => metadata_value
83
+ #
84
+ #===Outputs
85
+ # Returns true if the file was successfully saved. Note: true status does not necessarily indicate each field was successfully written.
86
+ #
87
+ #===Examples
88
+ # Music.writeMetadata("/absolute/path/to/file.mp3", {:artist => "NewArtistName", :year => "2019"})
89
+ #
90
+ def self.writeMetadata(filepath, meta = {})
91
+ attributes = {}
92
+ successflag = false
93
+ TagLib::FileRef.open(filepath) do |fileref|
94
+ unless fileref.null?
95
+ # sign tags to local variables
96
+ tag = fileref.tag
97
+ properties = fileref.audio_properties
111
98
 
112
- end
113
-
114
- end
99
+ meta.each do |field, value|
100
+ if tag.respond_to?(field)
101
+ tag.send("#{field}=", value)
102
+ elsif properties.respond_to?(field)
103
+ properties.send("#{field}=", value)
104
+ end
105
+ end
106
+ successflag = fileref.save
107
+ end
108
+ end
109
+ successflag
110
+ end
111
+ end
115
112
  end
metadata CHANGED
@@ -1,55 +1,52 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: media-organizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
5
- prerelease:
4
+ version: 0.1.6
6
5
  platform: ruby
7
6
  authors:
8
7
  - Stephen Johnson
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2015-03-24 00:00:00.000000000 Z
11
+ date: 2016-03-28 00:00:00.000000000 Z
13
12
  dependencies: []
14
- description: ! 'Provides a set of functions to scan file systems for media files,
15
- and dynamically rename them using their metadata. Files are renamed according to
16
- a customizable taxonomy. For example, use MediaOrganizer::Renamer to set filenames
17
- for a directory of photos to a standard such as: "<date-taken> - Ski Vacation.jpg".
18
- Currently supports only JPEG and TIFF files. Future releases will include support
19
- for music and additional image files.'
13
+ description: 'Dynamically renaming files using their metadata, according to a customizable
14
+ pattern. For example, use media-organizer to set filenames for a library of photos
15
+ to a customizable standard such as: "<date-taken> - Ski Vacation.jpg". Currently
16
+ supports only JPEG, TIFF, MP3, M4A, WAV, FLAC, and OGG files.'
20
17
  email: djeserkare@gmail.com
21
18
  executables: []
22
19
  extensions: []
23
20
  extra_rdoc_files: []
24
21
  files:
25
- - lib/renamer.rb
26
- - lib/media-organizer.rb
27
22
  - lib/filescanner.rb
28
- - lib/scrapers/music.rb
23
+ - lib/media-organizer.rb
24
+ - lib/metadata.rb
25
+ - lib/renamer.rb
29
26
  - lib/scrapers/image.rb
27
+ - lib/scrapers/music.rb
30
28
  homepage: http://rubygems.org/gems/media-organizer
31
29
  licenses:
32
30
  - MIT
31
+ metadata: {}
33
32
  post_install_message:
34
33
  rdoc_options: []
35
34
  require_paths:
36
35
  - lib
37
36
  required_ruby_version: !ruby/object:Gem::Requirement
38
- none: false
39
37
  requirements:
40
- - - ! '>='
38
+ - - ">="
41
39
  - !ruby/object:Gem::Version
42
40
  version: '0'
43
41
  required_rubygems_version: !ruby/object:Gem::Requirement
44
- none: false
45
42
  requirements:
46
- - - ! '>='
43
+ - - ">="
47
44
  - !ruby/object:Gem::Version
48
45
  version: '0'
49
46
  requirements: []
50
47
  rubyforge_project:
51
- rubygems_version: 1.8.23
48
+ rubygems_version: 2.4.5.1
52
49
  signing_key:
53
- specification_version: 3
54
- summary: Rename media files in bulk based on file metadata.
50
+ specification_version: 4
51
+ summary: Automatically rename music and image files in bulk based on file metadata.
55
52
  test_files: []