imagebackup 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +5 -3
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/imagebackup.gemspec +14 -12
- data/lib/classes/filetypes.rb +11 -8
- data/lib/imagebackup.rb +27 -37
- data/lib/imagebackup/version.rb +1 -1
- data/lib/methods/build_paths.rb +6 -4
- data/lib/methods/copy_pic.rb +11 -12
- data/lib/methods/display_help.rb +54 -54
- data/lib/methods/get_dates.rb +5 -5
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4a1d4dd786b4165fd4812dea1de48ef9ad9ad9040e2b4c572f633117e7147f9
|
4
|
+
data.tar.gz: 67d0e990a7491fee092a0b58386b218a52635ae1f68a79608dca347c791d8814
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b0b396b58101630b0c8390e9b0a6b8e139774b29d775136518becc866157bc66245682be814fc299b79095147f00c53d15a13079017fdc73b2973a8148aa511
|
7
|
+
data.tar.gz: d6c1d1dadc59c952dfca3d99f559fc69538459cde68d6268914a5e25bb4668fe5ec05d8e4f6e4d6914744cc91623331c79556bc3fbfc3b96c33d7322a9b7e38f
|
data/Gemfile
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
2
4
|
|
3
5
|
# Specify your gem's dependencies in imagebackup.gemspec
|
4
6
|
gemspec
|
5
7
|
|
6
|
-
gem
|
7
|
-
gem
|
8
|
+
gem 'rake', '~> 12.0'
|
9
|
+
gem 'rspec', '~> 3.0'
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'imagebackup'
|
5
6
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +11,5 @@ require "imagebackup"
|
|
10
11
|
# require "pry"
|
11
12
|
# Pry.start
|
12
13
|
|
13
|
-
require
|
14
|
+
require 'irb'
|
14
15
|
IRB.start(__FILE__)
|
data/imagebackup.gemspec
CHANGED
@@ -1,31 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'lib/imagebackup/version'
|
2
4
|
|
3
5
|
Gem::Specification.new do |spec|
|
4
|
-
spec.name =
|
6
|
+
spec.name = 'imagebackup'
|
5
7
|
spec.version = Imagebackup::VERSION
|
6
|
-
spec.authors = [
|
7
|
-
spec.email = [
|
8
|
+
spec.authors = ['adrian-sal-kennedy']
|
9
|
+
spec.email = ['adrian.sal.kennedy@gmail.com']
|
8
10
|
|
9
|
-
spec.summary =
|
10
|
-
spec.description =
|
11
|
-
spec.homepage =
|
12
|
-
spec.license =
|
13
|
-
spec.required_ruby_version = Gem::Requirement.new(
|
11
|
+
spec.summary = 'A simple CLI app to backup all pics and vids from current folder to a destination in yyyy-mm-dd folders.'
|
12
|
+
spec.description = 'Uses Exiv2 and ffprobe to find creation dates for media types it finds.'
|
13
|
+
spec.homepage = 'https://github.com/adrian-sal-kennedy/imagebackup'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
|
14
16
|
|
15
17
|
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
16
18
|
|
17
|
-
spec.metadata[
|
19
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
18
20
|
# spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
19
21
|
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
20
22
|
|
21
23
|
# Specify which files should be added to the gem when it is released.
|
22
24
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
-
spec.files
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
24
26
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
27
|
end
|
26
|
-
spec.bindir =
|
28
|
+
spec.bindir = 'exe'
|
27
29
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
-
spec.require_paths = [
|
30
|
+
spec.require_paths = ['lib']
|
29
31
|
|
30
32
|
spec.add_dependency 'exiv2'
|
31
33
|
spec.add_dependency 'ffprober'
|
data/lib/classes/filetypes.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'csv'
|
2
2
|
|
3
|
+
# FileTypes is a very simple file class to store and retrieve the list of files
|
4
|
+
# we are interested in backing up.
|
3
5
|
class FileTypes
|
4
6
|
def self.list
|
5
7
|
file_list = []
|
@@ -7,11 +9,11 @@ class FileTypes
|
|
7
9
|
file_list << "**/*.#{csv[0]}"
|
8
10
|
file_list << "**/*.#{csv[0].upcase}"
|
9
11
|
end
|
10
|
-
|
12
|
+
file_list
|
11
13
|
end
|
12
14
|
|
13
|
-
def self.add(ext=nil,type=nil)
|
14
|
-
unless ext
|
15
|
+
def self.add(ext = nil, type = nil)
|
16
|
+
unless ext
|
15
17
|
puts 'please enter the file type\'s extension, with or without the dot.'
|
16
18
|
ext = gets.strip
|
17
19
|
end
|
@@ -19,12 +21,13 @@ class FileTypes
|
|
19
21
|
puts 'please enter "movie" or "pic" so we know what we\'re working with.'
|
20
22
|
type = gets.strip
|
21
23
|
end
|
22
|
-
ext = ext.to_s.gsub(/[*."]/,'')
|
23
|
-
|
24
|
-
type = (type.downcase.include?('m')) ? "movie" : "pic"
|
24
|
+
ext = ext.to_s.gsub(/[*."]/, '')
|
25
|
+
type = type.downcase.include?('m') ? 'movie' : 'pic'
|
25
26
|
puts "opening file \"#{File.dirname(__FILE__)}/filetypes.csv\""
|
26
|
-
CSV.open("#{File.dirname(__FILE__)}/filetypes.csv",
|
27
|
-
|
27
|
+
CSV.open("#{File.dirname(__FILE__)}/filetypes.csv", 'a') do |csv|
|
28
|
+
csv << [ext, type]
|
28
29
|
end
|
30
|
+
puts 'New file types registered.'
|
31
|
+
exit
|
29
32
|
end
|
30
33
|
end
|
data/lib/imagebackup.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
|
2
3
|
require_relative 'imagebackup/version'
|
3
4
|
|
4
5
|
require_relative 'classes/filetypes'
|
@@ -11,56 +12,46 @@ require 'exiv2'
|
|
11
12
|
require 'fileutils'
|
12
13
|
require 'ffprober'
|
13
14
|
|
14
|
-
def main_loop(dest,dryrun=true,file_op=
|
15
|
+
def main_loop(dest, dryrun = true, file_op = 'copy')
|
15
16
|
file_types = FileTypes.list
|
16
17
|
Dir.glob(file_types).reverse_each do |f|
|
17
|
-
|
18
18
|
file = "#{Dir.pwd}/#{f}"
|
19
19
|
|
20
|
-
parms = build_paths(dest,file,get_dates(file))
|
20
|
+
parms = build_paths(dest, file, get_dates(file))
|
21
21
|
outfile = parms[0]
|
22
22
|
destpath = parms[1]
|
23
23
|
|
24
|
-
copy_pic(file,outfile,destpath,dryrun,file_op)
|
25
|
-
|
24
|
+
copy_pic(file, outfile, destpath, dryrun, file_op)
|
26
25
|
end
|
27
26
|
end
|
28
27
|
|
29
|
-
# main_loop(ARGV[0])
|
30
|
-
|
31
28
|
dryrun = true
|
32
|
-
#
|
33
|
-
# move = false
|
34
|
-
file_op = "cp" # or "mv" or "ln_s"
|
29
|
+
file_op = 'cp' # or "mv" or "ln_s"
|
35
30
|
|
36
|
-
if (ARGV & ['-m','--move']).any?
|
37
|
-
|
38
|
-
file_op = "mv"
|
31
|
+
if (ARGV & ['-m', '--move']).any?
|
32
|
+
file_op = 'mv'
|
39
33
|
ARGV.delete('-m')
|
40
34
|
ARGV.delete('--move')
|
41
35
|
end
|
42
|
-
if (ARGV & ['-l','--link']).any?
|
43
|
-
|
44
|
-
file_op = "ln_s"
|
36
|
+
if (ARGV & ['-l', '--link']).any?
|
37
|
+
file_op = 'ln_s'
|
45
38
|
ARGV.delete('-l')
|
46
39
|
ARGV.delete('--link')
|
47
40
|
end
|
48
41
|
if ARGV.include?('-a')
|
49
|
-
ext=ARGV[ARGV.index('-a')+1]
|
50
|
-
type=ARGV[ARGV.index('-a')+2]
|
51
|
-
ARGV.slice!(ARGV.index('-a')..ARGV.index('-a')+2)
|
42
|
+
ext = ARGV[ARGV.index('-a') + 1]
|
43
|
+
type = ARGV[ARGV.index('-a') + 2]
|
44
|
+
ARGV.slice!(ARGV.index('-a')..ARGV.index('-a') + 2)
|
52
45
|
end
|
53
46
|
if ARGV.include?('--add-filetype')
|
54
|
-
ext=ARGV[ARGV.index('--add-filetype')+1]
|
55
|
-
type=ARGV[ARGV.index('--add-filetype')+2]
|
56
|
-
ARGV.slice!(ARGV.index('--add-filetype')..ARGV.index('--add-filetype')+2)
|
57
|
-
end
|
58
|
-
if ext
|
59
|
-
FileTypes.add(ext,type)
|
47
|
+
ext = ARGV[ARGV.index('--add-filetype') + 1]
|
48
|
+
type = ARGV[ARGV.index('--add-filetype') + 2]
|
49
|
+
ARGV.slice!(ARGV.index('--add-filetype')..ARGV.index('--add-filetype') + 2)
|
60
50
|
end
|
61
|
-
|
51
|
+
FileTypes.add(ext, type) if ext
|
52
|
+
if (ARGV & ['-n', '--dry-run']).any?
|
62
53
|
dryrun = true
|
63
|
-
puts
|
54
|
+
puts 'Doing a dry run - no operations will happen.'
|
64
55
|
sleep 1
|
65
56
|
ARGV.delete('-n')
|
66
57
|
ARGV.delete('--dry-run')
|
@@ -68,16 +59,15 @@ else
|
|
68
59
|
dryrun = false
|
69
60
|
end
|
70
61
|
|
71
|
-
if (ARGV & ['-h','--help','-?']).any?
|
72
|
-
display_help()
|
73
|
-
end
|
62
|
+
display_help if (ARGV & ['-h', '--help', '-?']).any?
|
74
63
|
|
75
64
|
if ARGV[0].to_s == ''
|
76
|
-
display_help
|
65
|
+
display_help
|
66
|
+
elsif File.exist?(ARGV[0])
|
67
|
+
main_loop(ARGV[0], dryrun, file_op)
|
68
|
+
elsif ARGV[0][0] == '-'
|
69
|
+
puts "Invalid option!\n-----\n\n"
|
70
|
+
display_help
|
77
71
|
else
|
78
|
-
|
79
|
-
|
80
|
-
else
|
81
|
-
puts "Specified destination does not exist. Please create this folder first."
|
82
|
-
end
|
83
|
-
end
|
72
|
+
puts 'Specified destination does not exist. Please create this folder first.'
|
73
|
+
end
|
data/lib/imagebackup/version.rb
CHANGED
data/lib/methods/build_paths.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
def build_paths(dest, file, date)
|
2
4
|
date = get_dates(file)
|
3
5
|
destpath = "#{dest}/#{date}"
|
4
|
-
destpath = destpath.gsub('//','/')
|
6
|
+
destpath = destpath.gsub('//', '/')
|
5
7
|
outfile = "#{destpath}/#{File.basename(file)}"
|
6
|
-
|
7
|
-
end
|
8
|
+
[outfile, destpath]
|
9
|
+
end
|
data/lib/methods/copy_pic.rb
CHANGED
@@ -1,29 +1,28 @@
|
|
1
|
-
def process_file(file,outfile,dryrun=nil,file_op=
|
1
|
+
def process_file(file, outfile, dryrun = nil, file_op = 'cp')
|
2
2
|
if dryrun
|
3
3
|
puts "pretending to #{file_op} \"#{file} to #{outfile}\""
|
4
4
|
else
|
5
5
|
puts "#{file_op}-ing \"#{file} to #{outfile}\"..."
|
6
|
-
FileUtils.public_send(file_op,file,outfile)
|
7
|
-
FileUtils.public_send(file_op,"#{file}.xmp",outfile
|
8
|
-
if file_op ==
|
9
|
-
FileUtils.public_send(file_op,"#{file}.xmp",outfile)
|
6
|
+
FileUtils.public_send(file_op, file, outfile)
|
7
|
+
FileUtils.public_send(file_op, "#{file}.xmp", outfile, force: true)
|
8
|
+
if file_op == 'cp'
|
9
|
+
FileUtils.public_send(file_op, "#{file}.xmp", outfile)
|
10
10
|
else
|
11
|
-
FileUtils.public_send(file_op,"#{file}.xmp",outfile
|
11
|
+
FileUtils.public_send(file_op, "#{file}.xmp", outfile, force: true)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
def copy_pic(file,outfile,destpath,dryrun=nil,file_op=
|
17
|
-
|
16
|
+
def copy_pic(file, outfile, destpath, dryrun = nil, file_op = 'cp')
|
17
|
+
if File.exist?(outfile)
|
18
|
+
puts "\"#{outfile}\" already exists. Skipping..."
|
19
|
+
else
|
18
20
|
unless dryrun
|
19
21
|
unless File.exist?(destpath)
|
20
22
|
puts "creating folder \"#{destpath}\""
|
21
23
|
FileUtils.mkdir_p(destpath)
|
22
|
-
else
|
23
24
|
end
|
24
25
|
end
|
25
|
-
process_file(file,outfile,dryrun,file_op)
|
26
|
-
else
|
27
|
-
puts "\"#{outfile}\" already exists. Skipping..."
|
26
|
+
process_file(file, outfile, dryrun, file_op)
|
28
27
|
end
|
29
28
|
end
|
data/lib/methods/display_help.rb
CHANGED
@@ -1,82 +1,82 @@
|
|
1
1
|
def display_help
|
2
|
-
print
|
3
|
-
===== ImageBackup =====
|
2
|
+
print <<~HELPFILE
|
3
|
+
===== ImageBackup =====
|
4
4
|
|
5
|
-
Keep your photos and videos organized by date.
|
5
|
+
Keep your photos and videos organized by date.
|
6
6
|
|
7
|
-
A simple terminal app to crawl a folder (usually a camera card's DCIM folder) for pictures and videos, and pop them in dated folders to your destination folder of choice. Uses exif data when available, creation_time, or the file's own creation date if nothing better is present.
|
7
|
+
A simple terminal app to crawl a folder (usually a camera card's DCIM folder) for pictures and videos, and pop them in dated folders to your destination folder of choice. Uses exif data when available, creation_time, or the file's own creation date if nothing better is present.
|
8
8
|
|
9
|
-
Will also copy over any xmp sidecar files found, not overwriting.
|
9
|
+
Will also copy over any xmp sidecar files found, not overwriting.
|
10
10
|
|
11
|
-
Options:
|
11
|
+
Options:
|
12
12
|
|
13
|
-
-n, --dry-run Run without actually doing anything. Good for making sure
|
14
|
-
|
15
|
-
|
13
|
+
-n, --dry-run Run without actually doing anything. Good for making sure
|
14
|
+
things are working properly. This will also give you console
|
15
|
+
output which helps identify unreadable files.
|
16
16
|
|
17
|
-
-a, --add-filetype <extension> <type> This will add a custom file type to the list of files it's
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
-a, --add-filetype <extension> <type> This will add a custom file type to the list of files it's
|
18
|
+
looking for. If you have an arcane digital camera (we
|
19
|
+
currently support canon, sony, pentax but new stuff comes out
|
20
|
+
all the time), this will allow you to add your raw files.
|
21
|
+
You can also access *filetypes.csv* and add them manually,
|
22
|
+
but ensure there's a blank line at the end or this program
|
23
|
+
may behave badly.
|
24
24
|
|
25
|
-
-m will move (deleting the original), which is probably not a
|
26
|
-
|
25
|
+
-m will move (deleting the original), which is probably not a
|
26
|
+
good idea in most cases but still useful at times.
|
27
27
|
|
28
|
-
Usage:
|
28
|
+
Usage:
|
29
29
|
|
30
|
-
- To backup a camera card:
|
30
|
+
- To backup a camera card:
|
31
31
|
|
32
|
-
$ cd /media/username/EOS_DIGITAL/DCIM
|
33
|
-
$ imagebackup.rb ~/Photos/raw
|
32
|
+
$ cd /media/username/EOS_DIGITAL/DCIM
|
33
|
+
$ imagebackup.rb ~/Photos/raw
|
34
34
|
|
35
|
-
This will search all files within the DCIM folder, check them with either exiv2 (for stills) or ffprobe (for videos) and retrieve their creation dates.
|
36
|
-
It will then copy them to a folder of the form ~/Photos/raw/<yyyy>-<mm>-<dd>
|
37
|
-
If it's unable to find metadata in a file it will look at the file's creation time attribute, which is less reliable but usually ok.
|
35
|
+
This will search all files within the DCIM folder, check them with either exiv2 (for stills) or ffprobe (for videos) and retrieve their creation dates.
|
36
|
+
It will then copy them to a folder of the form ~/Photos/raw/<yyyy>-<mm>-<dd>
|
37
|
+
If it's unable to find metadata in a file it will look at the file's creation time attribute, which is less reliable but usually ok.
|
38
38
|
|
39
|
-
- To register a new file type:
|
39
|
+
- To register a new file type:
|
40
40
|
|
41
|
-
$ imagebackup.rb --add-filetype orf pic
|
42
|
-
or
|
43
|
-
$ imagebackup.rb -a orf pic
|
41
|
+
$ imagebackup.rb --add-filetype orf pic
|
42
|
+
or
|
43
|
+
$ imagebackup.rb -a orf pic
|
44
44
|
|
45
|
-
This will work with *.ORF, orf, ".orf" as it will strip off any unnecessary characters. It is case-insensitive.
|
45
|
+
This will work with *.ORF, orf, ".orf" as it will strip off any unnecessary characters. It is case-insensitive.
|
46
46
|
|
47
|
-
- To do a dry run, checking each file and destination but not actually copying:
|
47
|
+
- To do a dry run, checking each file and destination but not actually copying:
|
48
48
|
|
49
|
-
$ imagebackup.rb -n ~/Photos/raw
|
50
|
-
or
|
51
|
-
$ imagebackup.rb ~/Photos/raw --dry-run
|
49
|
+
$ imagebackup.rb -n ~/Photos/raw
|
50
|
+
or
|
51
|
+
$ imagebackup.rb ~/Photos/raw --dry-run
|
52
52
|
|
53
|
-
- To move files instead of copying them (careful!):
|
53
|
+
- To move files instead of copying them (careful!):
|
54
54
|
|
55
|
-
$ imagebackup.rb -m ~/Photos/raw
|
56
|
-
or
|
57
|
-
$ imagebackup.rb --move ~/Photos/raw
|
55
|
+
$ imagebackup.rb -m ~/Photos/raw
|
56
|
+
or
|
57
|
+
$ imagebackup.rb --move ~/Photos/raw
|
58
58
|
|
59
|
-
- To make symbolic links instead of copying files:
|
59
|
+
- To make symbolic links instead of copying files:
|
60
60
|
|
61
|
-
$ imagebackup.rb -l ~/Photos/raw
|
62
|
-
or
|
63
|
-
$ imagebackup.rb --link ~/Photos/raw
|
61
|
+
$ imagebackup.rb -l ~/Photos/raw
|
62
|
+
or
|
63
|
+
$ imagebackup.rb --link ~/Photos/raw
|
64
64
|
|
65
|
-
This mode can be useful if you want to operate on the files in one place but keep them on their media. Particularly useful for large movie files.
|
65
|
+
This mode can be useful if you want to operate on the files in one place but keep them on their media. Particularly useful for large movie files.
|
66
66
|
|
67
|
-
Dependencies:
|
67
|
+
Dependencies:
|
68
68
|
|
69
|
-
Ruby v2.3.0 or greater, plus gems:
|
70
|
-
|
71
|
-
|
69
|
+
Ruby v2.3.0 or greater, plus gems:
|
70
|
+
- exiv2
|
71
|
+
- ffprober
|
72
72
|
|
73
|
-
Ruby modules:
|
74
|
-
|
73
|
+
Ruby modules:
|
74
|
+
- fileutils
|
75
75
|
|
76
|
-
Bundler should handle all these dependencies. If for some reason it doesn't you can run this in terminal:
|
77
|
-
$ gem install exiv2 ffprober
|
76
|
+
Bundler should handle all these dependencies. If for some reason it doesn't you can run this in terminal:
|
77
|
+
$ gem install exiv2 ffprober
|
78
78
|
|
79
|
-
|
79
|
+
HELPFILE
|
80
80
|
|
81
|
-
exit(0)
|
82
|
-
end
|
81
|
+
exit(0)
|
82
|
+
end
|
data/lib/methods/get_dates.rb
CHANGED
@@ -3,15 +3,15 @@ def get_dates(file)
|
|
3
3
|
image = Exiv2::ImageFactory.open(file)
|
4
4
|
image.read_metadata
|
5
5
|
date = image.exif_data.find { |v| v[0] == 'Exif.Image.DateTime' }
|
6
|
-
date = date[1].split[0].gsub(':','-')
|
7
|
-
rescue
|
6
|
+
date = date[1].split[0].gsub(':', '-')
|
7
|
+
rescue StandardError
|
8
8
|
begin
|
9
9
|
probe = Ffprober::Parser.from_file(file)
|
10
10
|
date = probe.format.tags[:creation_time].split('T')[0]
|
11
|
-
rescue
|
11
|
+
rescue StandardError
|
12
12
|
fileobj = File.new(file)
|
13
13
|
date = "#{fileobj.stat.ctime.year}-#{fileobj.stat.ctime.month}-#{fileobj.stat.ctime.day}"
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
17
|
-
end
|
16
|
+
date
|
17
|
+
end
|