media-organizer 0.1.1 → 0.1.2
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.
- data/lib/filescanner.rb +93 -89
- data/lib/media-organizer.rb +3 -2
- data/lib/renamer.rb +183 -134
- data/lib/scrapers/image.rb +29 -26
- data/lib/scrapers/music.rb +104 -47
- metadata +2 -2
data/lib/filescanner.rb
CHANGED
@@ -5,108 +5,112 @@
|
|
5
5
|
require 'scrapers/image.rb'
|
6
6
|
require 'scrapers/music.rb'
|
7
7
|
|
8
|
-
class FileNotValidError < StandardError ; end
|
9
8
|
|
10
|
-
|
11
|
-
include Image
|
12
|
-
include Music
|
9
|
+
module MediaOrganizer
|
13
10
|
|
14
|
-
|
15
|
-
attr_accessor :source_list
|
11
|
+
class FileNotValidError < StandardError ; end
|
16
12
|
|
13
|
+
class Filescanner
|
14
|
+
include Image
|
15
|
+
include Music
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
@source_list = []
|
21
|
-
end
|
17
|
+
attr_reader :root_nodes
|
18
|
+
attr_accessor :source_list
|
22
19
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
#===Required
|
28
|
-
#(1) String: String containing the URI of the top of the directory tree to scan
|
29
|
-
#
|
30
|
-
#===Optional
|
31
|
-
#(2) Arguments Hash:
|
32
|
-
#*:mode => (:single) -- if set to :single, only the given URI will be scanned. Subdirectories will be ignored.
|
33
|
-
#*:music => (true, false) -- if true, music files will be included in the scan. Set to false to exclude music files. Defaults to true
|
34
|
-
#*:image => (true, false) -- if true, image files will be included in the scan. Set to false to exclude image files. Defaults to true
|
35
|
-
#
|
36
|
-
#==Outputs
|
37
|
-
#Returns array of strings, where each string is a file URI for a music or image file.
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#==Usage Example
|
41
|
-
#Filescanner.open("/absolute/path/for/top/of/directory/tree")
|
42
|
-
#
|
43
|
-
def open(uri = "", args = {})
|
44
|
-
unless !uri.nil? && uri.is_a?(String) && (File.directory?(uri) || File.exists?(uri))
|
45
|
-
raise FileNotFoundError, "Directory given (#{uri}) could not be accessed."
|
46
|
-
end
|
47
|
-
|
48
|
-
include_images = true unless args[:image] == false
|
49
|
-
include_music = true unless args[:music] == false
|
50
|
-
files = []
|
51
|
-
if args[:mode] == :single
|
52
|
-
files = Dir.glob("#{uri}/*")
|
53
|
-
else
|
54
|
-
files = Dir.glob("#{uri}/**/*")
|
20
|
+
|
21
|
+
def initialize()
|
22
|
+
@root_nodes = []
|
23
|
+
@source_list = []
|
55
24
|
end
|
56
|
-
|
57
|
-
#
|
58
|
-
|
59
|
-
|
60
|
-
|
25
|
+
|
26
|
+
#
|
27
|
+
#Filescanner.open(String, {}): scans directory tree for media files, starting at String specified in first argument.
|
28
|
+
#
|
29
|
+
#==Inputs
|
30
|
+
#===Required
|
31
|
+
#(1) String: String containing the URI of the top of the directory tree to scan
|
32
|
+
#
|
33
|
+
#===Optional
|
34
|
+
#(2) Arguments Hash:
|
35
|
+
#*:mode => (:single) -- if set to :single, only the given URI will be scanned. Subdirectories will be ignored.
|
36
|
+
#*:music => (true, false) -- if true, music files will be included in the scan. Set to false to exclude music files. Defaults to true
|
37
|
+
#*:image => (true, false) -- if true, image files will be included in the scan. Set to false to exclude image files. Defaults to true
|
38
|
+
#
|
39
|
+
#==Outputs
|
40
|
+
#Returns array of strings, where each string is a file URI for a music or image file.
|
41
|
+
#
|
42
|
+
#
|
43
|
+
#==Usage Example
|
44
|
+
#Filescanner.open("/absolute/path/for/top/of/directory/tree")
|
45
|
+
#
|
46
|
+
def open(uri = "", args = {})
|
47
|
+
unless !uri.nil? && uri.is_a?(String) && (File.directory?(uri) || File.exists?(uri))
|
48
|
+
raise FileNotFoundError, "Directory given (#{uri}) could not be accessed."
|
49
|
+
end
|
50
|
+
|
51
|
+
include_images = true unless args[:image] == false
|
52
|
+
include_music = true unless args[:music] == false
|
53
|
+
files = []
|
54
|
+
if args[:mode] == :single
|
55
|
+
files = Dir.glob("#{uri}/*")
|
56
|
+
else
|
57
|
+
files = Dir.glob("#{uri}/**/*")
|
61
58
|
end
|
59
|
+
|
60
|
+
#add all files found to @source_list, if they are music files
|
61
|
+
files.each do |f|
|
62
|
+
if (Music.is_music?(f) && include_music) || (Image.is_image?(f) && include_images)
|
63
|
+
@source_list << f
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
return @source_list
|
68
|
+
|
69
|
+
rescue FileNotFoundError => e
|
70
|
+
puts e.message
|
71
|
+
puts e.backtrace.inspect
|
72
|
+
return false
|
62
73
|
end
|
63
74
|
|
64
|
-
|
75
|
+
#alternative run mode. Add multiple "root" directories to scan at once
|
76
|
+
def addRoot(dir_uri)
|
77
|
+
unless !dir_uri.nil? && dir_uri.is_a?(String) && File.directory?(dir_uri)
|
78
|
+
raise FileNotFoundError, "Directory given (#{dir_uri}) could not be accessed."
|
79
|
+
end
|
80
|
+
@root_nodes << dir_uri
|
81
|
+
rescue FileNotFoundError => e
|
82
|
+
puts e.message
|
83
|
+
puts e.backtrace.inspect
|
84
|
+
return false
|
85
|
+
end
|
65
86
|
|
66
|
-
|
67
|
-
|
68
|
-
puts e.backtrace.inspect
|
69
|
-
return false
|
70
|
-
end
|
87
|
+
#<<(): synonym for addRoot(). Also a deformed emoticon.
|
88
|
+
# def << (dir_uri) addRoot(dir_uri) end
|
71
89
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
90
|
+
#
|
91
|
+
#multiscan(): scans multiple directories added to @root_nodes using the addRoot() method.
|
92
|
+
#
|
93
|
+
#==Inputs
|
94
|
+
#===Required
|
95
|
+
#none
|
96
|
+
#
|
97
|
+
#===Optional
|
98
|
+
#(1) Arguments Hash:
|
99
|
+
#*:mode => (:single, :multiple)
|
100
|
+
#*:music => (true, false) -- if true, music files will be included in the scan. Set to false to exclude music files. Defaults to true
|
101
|
+
#*:image => (true, false) -- if true, image files will be included in the scan. Set to false to exclude image files. Defaults to true
|
102
|
+
#
|
103
|
+
#==Outputs
|
104
|
+
#Array of strings, where each string is a file URI for a music or image file.
|
105
|
+
#
|
106
|
+
def multiscan(args = {})
|
107
|
+
@root_nodes.each do |uri|
|
108
|
+
open(uri, args)
|
76
109
|
end
|
77
|
-
@
|
78
|
-
rescue FileNotFoundError => e
|
79
|
-
puts e.message
|
80
|
-
puts e.backtrace.inspect
|
81
|
-
return false
|
82
|
-
end
|
83
|
-
|
84
|
-
#<<(): synonym for addRoot(). Also a deformed emoticon.
|
85
|
-
# def << (dir_uri) addRoot(dir_uri) end
|
86
|
-
|
87
|
-
#
|
88
|
-
#multiscan(): scans multiple directories added to @root_nodes using the addRoot() method.
|
89
|
-
#
|
90
|
-
#==Inputs
|
91
|
-
#===Required
|
92
|
-
#none
|
93
|
-
#
|
94
|
-
#===Optional
|
95
|
-
#(1) Arguments Hash:
|
96
|
-
#*:mode => (:single, :multiple)
|
97
|
-
#*:music => (true, false) -- if true, music files will be included in the scan. Set to false to exclude music files. Defaults to true
|
98
|
-
#*:image => (true, false) -- if true, image files will be included in the scan. Set to false to exclude image files. Defaults to true
|
99
|
-
#
|
100
|
-
#==Outputs
|
101
|
-
#Array of strings, where each string is a file URI for a music or image file.
|
102
|
-
#
|
103
|
-
def multiscan(args = {})
|
104
|
-
@root_nodes.each do |uri|
|
105
|
-
open(uri, args)
|
110
|
+
return @source_list
|
106
111
|
end
|
107
|
-
|
112
|
+
|
108
113
|
end
|
109
|
-
|
110
|
-
end
|
111
114
|
|
115
|
+
end
|
112
116
|
|
data/lib/media-organizer.rb
CHANGED
data/lib/renamer.rb
CHANGED
@@ -1,163 +1,212 @@
|
|
1
1
|
#renamer.rb: main codebase for the media-renamer gem.
|
2
2
|
#Currently configured to only rename JPG and TIF files (using EXIFR to extract metadata)
|
3
3
|
#next major release will include support
|
4
|
-
|
5
|
-
|
6
4
|
require 'scrapers/image.rb'
|
7
5
|
require 'scrapers/music.rb'
|
8
6
|
|
9
|
-
|
10
|
-
class InvalidArgumentError < StandardError ; end
|
11
|
-
class UnsupportedFileTypeError < StandardError ; end
|
12
|
-
class RenameFailedError < StandardError ; end
|
7
|
+
module MediaOrganizer
|
13
8
|
|
14
|
-
class
|
15
|
-
|
16
|
-
|
9
|
+
class FileNotValidError < StandardError ; end
|
10
|
+
class InvalidArgumentError < StandardError ; end
|
11
|
+
class UnsupportedFileTypeError < StandardError ; end
|
12
|
+
class RenameFailedError < StandardError ; end
|
13
|
+
|
14
|
+
#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.
|
15
|
+
#
|
16
|
+
#===Key Methods
|
17
|
+
# *setNamingScheme()
|
18
|
+
# *generateRenameList()
|
19
|
+
# *overwrite()
|
20
|
+
#
|
21
|
+
#===Example Usage
|
22
|
+
#
|
23
|
+
# old_uris = ['./test/data/hs-2003-24-a-full_tif.tif']
|
24
|
+
#
|
25
|
+
# scheme = ["Test-", :date_time]
|
26
|
+
#
|
27
|
+
# r = Renamer.new()
|
28
|
+
#
|
29
|
+
# r.setNamingScheme(scheme)
|
30
|
+
#
|
31
|
+
# new_uris = r.generateRenameList(old_uris)
|
32
|
+
#
|
33
|
+
# r.overwrite(new_uris) #new filename: "./test/data/Test-2003-09-03 12_52_43 -0400.tif")
|
34
|
+
#
|
35
|
+
class Renamer
|
36
|
+
DISALLOWED_CHARACTERS = /[\\:\?\*<>\|"\/]/ #Characters that are not allowed in file names by many file systems. Replaced with @subchar character.
|
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
|
17
45
|
|
18
|
-
|
46
|
+
#Renamer.setNamingScheme(): sets the naming scheme for the generateRenameList method.
|
47
|
+
#
|
48
|
+
#===Inputs
|
49
|
+
# 1. Array containing strings and symbols.
|
50
|
+
#
|
51
|
+
#===Outputs
|
52
|
+
#None (sets instance variable @naming_scheme)
|
53
|
+
#
|
54
|
+
#===Example
|
55
|
+
#setNamingScheme(["Vacation_Photos_", :date_taken]).
|
56
|
+
#This will rename files into a format like "Vacation_Photos_2014_05_22.png" based on the file's date_taken metadata field.
|
57
|
+
def setNamingScheme(arr = [])
|
58
|
+
@naming_scheme = setScheme(arr)
|
59
|
+
end
|
19
60
|
|
20
|
-
|
21
|
-
|
61
|
+
#Renamer.generateRenameList(): Creates a hash mapping the original filenames to the new (renamed) filenames
|
62
|
+
#
|
63
|
+
#===Inputs
|
64
|
+
# 1. List of URIs in the form of an array
|
65
|
+
# 2. Optional hash of arguments.
|
66
|
+
# *:scheme - array of strings and symbols specifying file naming convention
|
67
|
+
#
|
68
|
+
#===Outputs
|
69
|
+
#Hash of "file name pairs." old_file => new_file
|
70
|
+
def generateRenameList(uri_list = [], args = {})
|
71
|
+
if args[:scheme] != nil && args[:scheme].is_a?(Array) && !args[:scheme].empty?
|
72
|
+
scheme = setScheme(args[:scheme])
|
73
|
+
else
|
74
|
+
scheme = @naming_scheme
|
75
|
+
end
|
76
|
+
unless !uri_list.nil? && uri_list.is_a?(Array)
|
77
|
+
raise InvalidArgumentError
|
78
|
+
end
|
22
79
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
80
|
+
filename_pairs = {}
|
81
|
+
uri_list.each do |i|
|
82
|
+
new_string = handleFile(i, scheme)
|
83
|
+
#If this is a valid file path, add it to the filename_pairs
|
84
|
+
#puts "New file rename added: #{new_string}"
|
85
|
+
if new_string != nil && new_string != ""
|
86
|
+
filename_pairs[i] = new_string
|
87
|
+
end
|
88
|
+
end
|
27
89
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
scheme = @naming_scheme
|
39
|
-
end
|
40
|
-
unless !uri_list.nil? && uri_list.is_a?(Array)
|
41
|
-
raise InvalidArgumentError
|
90
|
+
return filename_pairs
|
91
|
+
|
92
|
+
rescue InvalidArgumentError => arg_e
|
93
|
+
puts arg_e
|
94
|
+
puts "Invalid arguments provided. Expected: uri_list = [], args = {}"
|
95
|
+
puts arg_e.backtrace.inspect
|
96
|
+
rescue => e
|
97
|
+
puts e
|
98
|
+
puts e.message
|
99
|
+
puts e.backtrace.inspect
|
42
100
|
end
|
43
101
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
102
|
+
#Renamer.overwrite(): Writes new file names based upon mapping provided in hash argument. NOTE: this will create changes to file names!
|
103
|
+
#
|
104
|
+
#===Inputs
|
105
|
+
# 1. Hash containing mappings between old filenames (full URI) and new filenames (full URI). Example: {"/path/to/oldfile.jpg" => "/path/to/newfile.jpg"}
|
106
|
+
#
|
107
|
+
#===Outputs
|
108
|
+
#none (file names are overwritten)
|
109
|
+
def overwrite(renames_hash = {})
|
110
|
+
renames_hash.each do |old_name, new_name|
|
111
|
+
begin
|
112
|
+
#error/integrity checking on old_name and new_name
|
113
|
+
raise FileNotValidError, "Could not access specified source file: #{i}." unless old_name.is_a?(String) && File.exists?(old_name)
|
114
|
+
raise FileNotValidError, "New file name provided is not a string" unless new_name.is_a?(String)
|
115
|
+
|
116
|
+
#puts (File.dirname(File.absolute_path(old_name)) + "/" + new_name) #Comment this line out unless testing
|
117
|
+
File.rename(File.absolute_path(old_name),File.dirname(File.absolute_path(old_name)) + "/" + new_name)
|
118
|
+
|
119
|
+
#check that renamed file exists - Commented out because this currently does not work.
|
120
|
+
#unless new_name.is_a?(String) && File.exists?(new_name)
|
121
|
+
# raise RenameFailedError, "Could not successfuly rename file: #{old_name} => #{new_name}. Invalid URI or file does not exist."
|
122
|
+
#end
|
123
|
+
rescue => e
|
124
|
+
puts "Ignoring rename for #{old_name} => #{new_name}"
|
125
|
+
puts e
|
126
|
+
puts e.backtrace.inspect
|
127
|
+
end
|
51
128
|
end
|
52
129
|
end
|
53
130
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
File.rename(File.absolute_path(old_name),File.dirname(File.absolute_path(old_name)) + "/" + new_name)
|
75
|
-
|
76
|
-
#check that renamed file exists - Commented out because this currently does not work.
|
77
|
-
#unless new_name.is_a?(String) && File.exists?(new_name)
|
78
|
-
# raise RenameFailedError, "Could not successfuly rename file: #{old_name} => #{new_name}. Invalid URI or file does not exist."
|
79
|
-
#end
|
80
|
-
rescue => e
|
81
|
-
puts "Ignoring rename for #{old_name} => #{new_name}"
|
82
|
-
puts e
|
83
|
-
puts e.backtrace.inspect
|
131
|
+
#Routes metadata scrape based on file type (currently relies on extension - future version should use MIME)
|
132
|
+
#currently assumes file was checked for validity in calling code.
|
133
|
+
#
|
134
|
+
#===Inputs
|
135
|
+
#String containing full file URI (path and filename)
|
136
|
+
#
|
137
|
+
#===Outputs
|
138
|
+
#Returns hash of metadata for file, or nil if none/error.
|
139
|
+
def getFileMetadata(file)
|
140
|
+
|
141
|
+
#LOAD EXIF DATA
|
142
|
+
case File.extname(file).downcase
|
143
|
+
when '.jpg'
|
144
|
+
Image::getJpegData(file)
|
145
|
+
when '.tif'
|
146
|
+
Image::getTiffData(file)
|
147
|
+
when '.mp3' , '.wav' , '.flac' , '.aiff', '.ogg', '.m4a', '.asf'
|
148
|
+
Music::getMusicData(file)
|
149
|
+
else
|
150
|
+
raise UnsupportedFileTypeError, "Error processing #{file}"
|
84
151
|
end
|
152
|
+
rescue UnsupportedFileTypeError => e
|
153
|
+
puts "Could not process file: Extension #{File.extname(file)} is not supported."
|
154
|
+
puts e.backtrace.inspect
|
85
155
|
end
|
86
|
-
end
|
87
|
-
|
88
|
-
#Routes metadata scrape based on file type (currently relies on extension - future version should use MIME)
|
89
|
-
#currently assumes file was checked for validity in calling code
|
90
|
-
def getFileMetadata(file)
|
91
|
-
|
92
|
-
#LOAD EXIF DATA
|
93
|
-
case File.extname(file).downcase
|
94
|
-
when '.jpg'
|
95
|
-
Image::getJpegData(file)
|
96
|
-
when '.tif'
|
97
|
-
Image::getTiffData(file)
|
98
|
-
when '.mp3' , '.wav' , '.flac' , '.aiff', '.ogg', '.m4a', '.asf'
|
99
|
-
Music::getMusicData(file)
|
100
|
-
else
|
101
|
-
raise UnsupportedFileTypeError, "Error processing #{file}"
|
102
|
-
end
|
103
|
-
#otherwise, outsource
|
104
|
-
rescue UnsupportedFileTypeError => e
|
105
|
-
puts "Could not process file: Extension #{File.extname(file)} is not supported."
|
106
|
-
puts e.backtrace.inspect
|
107
|
-
end
|
108
156
|
|
109
157
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
158
|
+
private
|
159
|
+
def handleFile(file, scheme)
|
160
|
+
#Check file is real
|
161
|
+
unless file.is_a?(String) && File.exists?(file)
|
162
|
+
raise FileNotValidError, "Could not access specified file file: #{file}."
|
163
|
+
end
|
164
|
+
#convert URI (i) to absolute path
|
165
|
+
|
166
|
+
#get metadata hash for this file (i)
|
167
|
+
metadata = getFileMetadata(File.absolute_path(file))
|
168
|
+
#build URI string
|
169
|
+
new_string = ""
|
170
|
+
scheme.each do |j|
|
171
|
+
if j.is_a?(String) then new_string += j
|
172
|
+
elsif j.is_a?(Symbol)
|
173
|
+
begin
|
174
|
+
raise EmptyMetadataError unless metadata[j] != nil
|
175
|
+
new_string += metadata[j].to_s
|
176
|
+
rescue => e
|
177
|
+
puts "Could not get string for metadata tag provided in scheme: #{j} for file #{file}."
|
178
|
+
puts "Ignoring file #{file}"
|
179
|
+
puts e.backtrace.inspect
|
180
|
+
return nil
|
181
|
+
end
|
133
182
|
end
|
134
183
|
end
|
184
|
+
#puts "Found file metadata: #{metadata[:date_time]}"
|
185
|
+
return subHazardousChars(new_string + File.extname(file))
|
186
|
+
rescue FileNotValidError => e
|
187
|
+
puts ("Ignoring file #{file}")
|
188
|
+
puts e
|
189
|
+
puts e.backtrace
|
190
|
+
return nil
|
191
|
+
rescue => e
|
192
|
+
puts e.message
|
193
|
+
puts e.backtrace.inspect
|
194
|
+
return nil
|
135
195
|
end
|
136
|
-
#puts "Found file metadata: #{metadata[:date_time]}"
|
137
|
-
return subHazardousChars(new_string + File.extname(file))
|
138
|
-
rescue FileNotValidError => e
|
139
|
-
puts ("Ignoring file #{file}")
|
140
|
-
puts e
|
141
|
-
puts e.backtrace
|
142
|
-
return nil
|
143
|
-
rescue => e
|
144
|
-
puts e.message
|
145
|
-
puts e.backtrace.inspect
|
146
|
-
return nil
|
147
|
-
end
|
148
196
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
197
|
+
def setScheme(input_arr = [])
|
198
|
+
clean_scheme = []
|
199
|
+
input_arr.each do |i|
|
200
|
+
if i.is_a?(String) || i.is_a?(Symbol)
|
201
|
+
clean_scheme << i
|
202
|
+
end
|
154
203
|
end
|
204
|
+
return clean_scheme
|
155
205
|
end
|
156
|
-
return clean_scheme
|
157
|
-
end
|
158
206
|
|
159
|
-
|
160
|
-
|
207
|
+
def subHazardousChars(str = "")
|
208
|
+
return str.gsub(DISALLOWED_CHARACTERS, @subchar)
|
209
|
+
end
|
161
210
|
end
|
162
|
-
end
|
163
211
|
|
212
|
+
end
|
data/lib/scrapers/image.rb
CHANGED
@@ -2,40 +2,43 @@ require 'exifr'
|
|
2
2
|
|
3
3
|
class FileNotFoundError < StandardError ; end
|
4
4
|
|
5
|
-
module
|
6
|
-
SUPPORTED_FILETYPES = %w{.jpg .tif}
|
5
|
+
module MediaOrganizer
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
return meta.to_hash
|
11
|
-
#!!! Rescue from common file-related and exifr-related errors here
|
12
|
-
end
|
7
|
+
module Image
|
8
|
+
SUPPORTED_FILETYPES = %w{.jpg .tif}
|
13
9
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
def Image.getJpegData(file)
|
11
|
+
meta = EXIFR::JPEG.new(file)
|
12
|
+
return meta.to_hash
|
13
|
+
#!!! Rescue from common file-related and exifr-related errors here
|
14
|
+
end
|
19
15
|
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
def Image.getTiffData(file)
|
17
|
+
meta = EXIFR::TIFF.new(file)
|
18
|
+
return meta.to_hash
|
19
|
+
#!!! Rescue from common file-related and exifr-related errors here
|
20
|
+
end
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
raise FileNotFoundError, "Directory given (#{uri}) could not be accessed."
|
22
|
+
def Image.supported_filetypes
|
23
|
+
return SUPPORTED_FILETYPES
|
27
24
|
end
|
28
25
|
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
def Image.is_image?(uri)
|
27
|
+
unless !uri.nil? && uri.is_a?(String) && File.exists?(uri)
|
28
|
+
raise FileNotFoundError, "Directory given (#{uri}) could not be accessed."
|
29
|
+
end
|
30
|
+
|
31
|
+
if SUPPORTED_FILETYPES.include?(File.extname(uri).downcase)
|
32
|
+
return true
|
33
|
+
else
|
34
|
+
return false
|
35
|
+
end
|
36
|
+
|
37
|
+
rescue FileNotFoundError => e
|
38
|
+
puts e.message
|
39
|
+
puts e.backtrace.inspect
|
32
40
|
return false
|
33
41
|
end
|
34
42
|
|
35
|
-
rescue FileNotFoundError => e
|
36
|
-
puts e.message
|
37
|
-
puts e.backtrace.inspect
|
38
|
-
return false
|
39
43
|
end
|
40
|
-
|
41
44
|
end
|
data/lib/scrapers/music.rb
CHANGED
@@ -1,59 +1,116 @@
|
|
1
1
|
require 'taglib'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
module Music
|
6
|
-
|
7
|
-
SUPPORTED_FILETYPES = %w{.mp3 .m4a .mp4 .flac .m4a .ogg .aiff .asf .wav}
|
8
|
-
|
9
|
-
def Music.getMusicData(file)
|
10
|
-
attributes = {}
|
11
|
-
TagLib::FileRef.open(file) do |fileref|
|
12
|
-
unless fileref.null?
|
13
|
-
#sign tags to local variables
|
14
|
-
tag = fileref.tag
|
15
|
-
properties = fileref.audio_properties
|
16
|
-
|
17
|
-
#load tags into attributes attribute
|
18
|
-
attributes[:track_name] = tag.title
|
19
|
-
attributes[:track_number] = tag.track
|
20
|
-
attributes[:track_genre] = tag.genre
|
21
|
-
attributes[:track_release_date] = tag.year
|
22
|
-
attributes[:album_name] = tag.album
|
23
|
-
attributes[:artist_name] = tag.artist
|
24
|
-
attributes[:comment] = tag.comment
|
25
|
-
|
26
|
-
attributes[:track_length] = properties.length
|
27
|
-
attributes[:track_bitrate] = properties.bitrate
|
28
|
-
attributes[:track_channels] = properties.channels
|
29
|
-
attributes[:track_sample_rate] = properties.sample_rate
|
30
|
-
|
31
|
-
end
|
32
|
-
end
|
33
|
-
return attributes
|
34
|
-
end
|
3
|
+
module MediaOrganizer
|
35
4
|
|
36
|
-
|
37
|
-
|
38
|
-
|
5
|
+
class FileNotFoundError < StandardError ; end
|
6
|
+
|
7
|
+
module Music
|
8
|
+
|
9
|
+
SUPPORTED_FILETYPES = %w{.mp3 .m4a .mp4 .flac .m4a .ogg .aiff .asf .wav}
|
39
10
|
|
40
|
-
|
41
|
-
|
42
|
-
|
11
|
+
def Music.getMusicData(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
|
18
|
+
|
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
|
27
|
+
|
28
|
+
attributes[:length] = properties.length
|
29
|
+
attributes[:bitrate] = properties.bitrate
|
30
|
+
attributes[:channels] = properties.channels
|
31
|
+
attributes[:sample_rate] = properties.sample_rate
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
return attributes
|
43
36
|
end
|
44
37
|
|
45
|
-
|
46
|
-
|
47
|
-
|
38
|
+
def Music.supported_filetypes
|
39
|
+
reutrn SUPPORTED_FILETYPES
|
40
|
+
end
|
41
|
+
|
42
|
+
def Music.is_music?(uri)
|
43
|
+
unless !uri.nil? && uri.is_a?(String) && File.exists?(uri)
|
44
|
+
raise FileNotFoundError, "Directory given (#{uri}) could not be accessed."
|
45
|
+
end
|
46
|
+
|
47
|
+
if SUPPORTED_FILETYPES.include?(File.extname(uri).downcase)
|
48
|
+
return true
|
49
|
+
else
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
|
53
|
+
rescue FileNotFoundError => e
|
54
|
+
puts e.message
|
55
|
+
puts e.backtrace.inspect
|
48
56
|
return false
|
49
57
|
end
|
50
58
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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 Music.availableMetadata(filepath = "", args = {})
|
67
|
+
attrs = getMusicData(filepath)
|
68
|
+
|
69
|
+
unless args[:include_null] == false
|
70
|
+
attrs.each do |field, value|
|
71
|
+
if value == nil || value == ""
|
72
|
+
attrs.delete(field)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
return attrs
|
77
|
+
end
|
56
78
|
|
79
|
+
#
|
80
|
+
#writeMetadata(file = "", meta = {}): returns list of fields available as metadata for the given file.
|
81
|
+
#
|
82
|
+
#===Inputs
|
83
|
+
# *(1) filepath: full/absolute URI of the file to be analyzed. Required input.
|
84
|
+
# *(2) meta: metadata to be written, passed as a hash in the format :metadata_field => metadata_value
|
85
|
+
#
|
86
|
+
#===Outputs
|
87
|
+
#Returns true if the file was successfully saved. Note: true status does not necessarily indicate each field was successfully written.
|
88
|
+
#
|
89
|
+
#===Examples
|
90
|
+
#Music.writeMetadata("/absolute/path/to/file.mp3", {:artist => "NewArtistName", :year => "2019"})
|
91
|
+
#
|
92
|
+
def Music.writeMetadata(filepath, meta = {})
|
93
|
+
attributes = {}
|
94
|
+
successflag = false
|
95
|
+
TagLib::FileRef.open(filepath) do |fileref|
|
96
|
+
unless fileref.null?
|
97
|
+
#sign tags to local variables
|
98
|
+
tag = fileref.tag
|
99
|
+
properties = fileref.audio_properties
|
57
100
|
|
58
|
-
|
101
|
+
meta.each do |field, value|
|
102
|
+
if tag.respond_to?(field)
|
103
|
+
tag.send("#{field.to_s}=", value)
|
104
|
+
elsif properties.respond_to?(field)
|
105
|
+
properties.send("#{field.to_s}=", value)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
successflag = fileref.save
|
109
|
+
end
|
110
|
+
end
|
111
|
+
return successflag
|
59
112
|
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: media-organizer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-03-
|
12
|
+
date: 2015-03-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: taglib-ruby
|