media-organizer 0.1.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.
@@ -0,0 +1,112 @@
1
+ #
2
+ #filescanner.rb: defines the class Filescanner, which scans directory trees for media files
3
+ #
4
+
5
+ require 'scrapers/image.rb'
6
+ require 'scrapers/music.rb'
7
+
8
+ class FileNotValidError < StandardError ; end
9
+
10
+ class Filescanner
11
+ include Image
12
+ include Music
13
+
14
+ attr_reader :root_nodes
15
+ attr_accessor :source_list
16
+
17
+
18
+ def initialize()
19
+ @root_nodes = []
20
+ @source_list = []
21
+ end
22
+
23
+ #
24
+ #Filescanner.open(String, {}): scans directory tree for media files, starting at String specified in first argument.
25
+ #
26
+ #==Inputs
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}/**/*")
55
+ end
56
+
57
+ #add all files found to @source_list, if they are music files
58
+ files.each do |f|
59
+ if (Music.is_music?(f) && include_music) || (Image.is_image?(f) && include_images)
60
+ @source_list << f
61
+ end
62
+ end
63
+
64
+ return @source_list
65
+
66
+ rescue FileNotFoundError => e
67
+ puts e.message
68
+ puts e.backtrace.inspect
69
+ return false
70
+ end
71
+
72
+ #alternative run mode. Add multiple "root" directories to scan at once
73
+ def addRoot(dir_uri)
74
+ unless !dir_uri.nil? && dir_uri.is_a?(String) && File.directory?(dir_uri)
75
+ raise FileNotFoundError, "Directory given (#{dir_uri}) could not be accessed."
76
+ end
77
+ @root_nodes << dir_uri
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)
106
+ end
107
+ return @source_list
108
+ end
109
+
110
+ end
111
+
112
+
@@ -0,0 +1,9 @@
1
+ #media-renamer.rb
2
+
3
+ require 'renamer.rb'
4
+ require 'filescanner.rb'
5
+
6
+ class MediaRenamer < Renamer
7
+
8
+ end
9
+
data/lib/renamer.rb ADDED
@@ -0,0 +1,158 @@
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
+
5
+
6
+ require 'scrapers/image.rb'
7
+ require 'scrapers/music.rb'
8
+
9
+ class FileNotValidError < StandardError ; end
10
+ class InvalidArgumentError < StandardError ; end
11
+ class UnsupportedFileTypeError < StandardError ; end
12
+ class RenameFailedError < StandardError ; end
13
+
14
+
15
+ class Renamer
16
+ include Image
17
+ include Music
18
+ attr_accessor :naming_scheme # => array of strings and literals used to construct filenames
19
+
20
+ def initialize
21
+ @naming_scheme = ["Renamed-default-"]
22
+ end
23
+
24
+ def setNamingScheme(arr = [])
25
+ @naming_scheme = setScheme(arr)
26
+ end
27
+ #Input: list of URIs in the form of an array
28
+ #Output: hash of "file name pairs." old_file => new_file
29
+ #Accepts optional arguments: :scheme (array of strings and symbols specifying file naming convention)
30
+ def generateRenameList(uri_list = [], args = {})
31
+ if args[:scheme] != nil && args[:scheme].is_a?(Array) && !args[:scheme].empty?
32
+ scheme = setScheme(args[:scheme])
33
+ else
34
+ scheme = @naming_scheme
35
+ end
36
+ unless !uri_list.nil? && uri_list.is_a?(Array)
37
+ raise InvalidArgumentError
38
+ end
39
+
40
+ filename_pairs = {}
41
+ uri_list.each do |i|
42
+ new_string = handleFile(i, scheme)
43
+ #If this is a valid file path, add it to the filename_pairs
44
+ #puts "New file rename added: #{new_string}"
45
+ if new_string != nil && new_string != ""
46
+ filename_pairs[i] = new_string
47
+ end
48
+ end
49
+
50
+ return filename_pairs
51
+
52
+ rescue InvalidArgumentError => arg_e
53
+ puts arg_e
54
+ puts "Invalid arguments provided. Expected: uri_list = [], args = {}"
55
+ puts arg_e.backtrace.inspect
56
+ rescue => e
57
+ puts e
58
+ puts e.message
59
+ puts e.backtrace.inspect
60
+ end
61
+
62
+ def overwrite(renames_hash = {})
63
+ renames_hash.each do |old_name, new_name|
64
+ begin
65
+ #error/integrity checking on old_name and new_name
66
+ raise FileNotValidError, "Could not access specified source file: #{i}." unless old_name.is_a?(String) && File.exists?(old_name)
67
+ raise FileNotValidError, "New file name provided is not a string" unless new_name.is_a?(String)
68
+
69
+ #puts (File.dirname(File.absolute_path(old_name)) + "/" + new_name) #Comment this line out unless testing
70
+ File.rename(File.absolute_path(old_name),File.dirname(File.absolute_path(old_name)) + "/" + new_name)
71
+
72
+ #check that renamed file exists - Commented out because this currently does not work.
73
+ #unless new_name.is_a?(String) && File.exists?(new_name)
74
+ # raise RenameFailedError, "Could not successfuly rename file: #{old_name} => #{new_name}. Invalid URI or file does not exist."
75
+ #end
76
+ rescue => e
77
+ puts "Ignoring rename for #{old_name} => #{new_name}"
78
+ puts e
79
+ puts e.backtrace.inspect
80
+ end
81
+ end
82
+ end
83
+
84
+ #Routes metadata scrape based on file type (currently relies on extension - future version should use MIME)
85
+ #currently assumes file was checked for validity in calling code
86
+ def getFileMetadata(file)
87
+
88
+ #LOAD EXIF DATA
89
+ case File.extname(file).downcase
90
+ when '.jpg'
91
+ Image::getJpegData(file)
92
+ when '.tif'
93
+ Image::getTiffData(file)
94
+ when '.mp3' , '.wav' , '.flac' , '.aiff', '.ogg', '.m4a', '.asf'
95
+ Music::getMusicData(file)
96
+ else
97
+ raise UnsupportedFileTypeError, "Error processing #{file}"
98
+ end
99
+ #otherwise, outsource
100
+ rescue UnsupportedFileTypeError => e
101
+ puts "Could not process file: Extension #{File.extname(file)} is not supported."
102
+ puts e.backtrace.inspect
103
+ end
104
+
105
+
106
+ private
107
+ def handleFile(file, scheme)
108
+ #Check file is real
109
+ unless file.is_a?(String) && File.exists?(file)
110
+ raise FileNotValidError, "Could not access specified file file: #{file}."
111
+ end
112
+ #convert URI (i) to absolute path
113
+
114
+ #get metadata hash for this file (i)
115
+ metadata = getFileMetadata(File.absolute_path(file))
116
+ #build URI string
117
+ new_string = ""
118
+ scheme.each do |j|
119
+ if j.is_a?(String) then new_string += j
120
+ elsif j.is_a?(Symbol)
121
+ begin
122
+ raise EmptyMetadataError unless metadata[j] != nil
123
+ new_string += metadata[j].to_s
124
+ rescue => e
125
+ puts "Could not get string for metadata tag provided in scheme: #{j} for file #{file}."
126
+ puts "Ignoring file #{file}"
127
+ puts e.backtrace.inspect
128
+ return nil
129
+ end
130
+ end
131
+ end
132
+ #puts "Found file metadata: #{metadata[:date_time]}"
133
+ return new_string + File.extname(file)
134
+ rescue FileNotValidError => e
135
+ puts ("Ignoring file #{file}")
136
+ puts e
137
+ puts e.backtrace
138
+ return nil
139
+ rescue => e
140
+ puts e.message
141
+ puts e.backtrace.inspect
142
+ return nil
143
+ end
144
+
145
+ def setScheme(input_arr = [])
146
+ clean_scheme = []
147
+ input_arr.each do |i|
148
+ if i.is_a?(String) || i.is_a?(Symbol)
149
+ clean_scheme << i
150
+ end
151
+ end
152
+ return clean_scheme
153
+ end
154
+ end
155
+
156
+
157
+
158
+
@@ -0,0 +1,41 @@
1
+ require 'exifr'
2
+
3
+ class FileNotFoundError < StandardError ; end
4
+
5
+ module Image
6
+ SUPPORTED_FILETYPES = %w{.jpg .tif}
7
+
8
+ def Image.getJpegData(file)
9
+ meta = EXIFR::JPEG.new(file)
10
+ return meta.to_hash
11
+ #!!! Rescue from common file-related and exifr-related errors here
12
+ end
13
+
14
+ def Image.getTiffData(file)
15
+ meta = EXIFR::TIFF.new(file)
16
+ return meta.to_hash
17
+ #!!! Rescue from common file-related and exifr-related errors here
18
+ end
19
+
20
+ def Image.supported_filetypes
21
+ return SUPPORTED_FILETYPES
22
+ end
23
+
24
+ def Image.is_image?(uri)
25
+ unless !uri.nil? && uri.is_a?(String) && File.exists?(uri)
26
+ raise FileNotFoundError, "Directory given (#{uri}) could not be accessed."
27
+ end
28
+
29
+ if SUPPORTED_FILETYPES.include?(File.extname(uri).downcase)
30
+ return true
31
+ else
32
+ return false
33
+ end
34
+
35
+ rescue FileNotFoundError => e
36
+ puts e.message
37
+ puts e.backtrace.inspect
38
+ return false
39
+ end
40
+
41
+ end
@@ -0,0 +1,59 @@
1
+ require 'taglib'
2
+
3
+ class FileNotFoundError < StandardError ; end
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
35
+
36
+ def Music.supported_filetypes
37
+ reutrn SUPPORTED_FILETYPES
38
+ end
39
+
40
+ def Music.is_music?(uri)
41
+ unless !uri.nil? && uri.is_a?(String) && File.exists?(uri)
42
+ raise FileNotFoundError, "Directory given (#{uri}) could not be accessed."
43
+ end
44
+
45
+ if SUPPORTED_FILETYPES.include?(File.extname(uri).downcase)
46
+ return true
47
+ else
48
+ return false
49
+ end
50
+
51
+ rescue FileNotFoundError => e
52
+ puts e.message
53
+ puts e.backtrace.inspect
54
+ return false
55
+ end
56
+
57
+
58
+ end
59
+
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: media-organizer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Stephen Johnson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-02-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: taglib-ruby
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: exifr
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: ! 'Provides a set of functions for scanning directory trees and dynamically
47
+ renaming files using their metadata, according to a customizable taxonomy. For example,
48
+ use media-organizer to set filenames for a directory of photos to a standard such
49
+ as: "<date-taken> - Ski Vacation.jpg". Currently supports only JPEG and TIFF files,
50
+ and various music formats.'
51
+ email: djeserkare@gmail.com
52
+ executables: []
53
+ extensions: []
54
+ extra_rdoc_files: []
55
+ files:
56
+ - lib/media-organizer.rb
57
+ - lib/renamer.rb
58
+ - lib/filescanner.rb
59
+ - lib/scrapers/image.rb
60
+ - lib/scrapers/music.rb
61
+ homepage: http://rubygems.org/gems/media-organizer
62
+ licenses:
63
+ - MIT
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 1.8.23
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: Organize & rename files in bulk based on file metadata.
86
+ test_files: []