photein 0.0.9 → 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.
- checksums.yaml +4 -4
- data/bin/photein +3 -7
- data/lib/photein/config.rb +24 -10
- data/lib/photein/image.rb +4 -6
- data/lib/photein/media_file.rb +39 -42
- data/lib/photein/version.rb +1 -1
- data/lib/photein/video.rb +4 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90719855955bcdf3585080664753c65528e9edaad99d2646498965c063ae0299
|
4
|
+
data.tar.gz: 874d2e1d44defc090968d10f4c000f84c7501d4f5d3a1bff6b2a2145297bf169
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a70c02ce6b74cd0cf58f887d4bf6ba21bdf0df50651f6acd230aa0e6382d6bdf3669d637f4f112acdfc4c5a6d8417425cf7a87f0b28dd6df5383cd539fd339f
|
7
|
+
data.tar.gz: 0a69d825de1c6f7fb4d81add6db960d2a12793b46e1aa6bb277193511dea0a59bcde2b47b97313419a268b6f38e97cc99e5b75642ebef3cbb88a445d4e1de446
|
data/bin/photein
CHANGED
@@ -9,13 +9,9 @@ Photein.logger.open
|
|
9
9
|
|
10
10
|
# Setup ------------------------------------------------------------------------
|
11
11
|
|
12
|
-
SRC_DIR = Pathname(Photein::Config.source)
|
13
|
-
DEST_DIR = Pathname(Photein::Config.dest)
|
14
|
-
|
15
12
|
begin
|
16
|
-
raise "#{Photein::Config.source}: no such directory" unless
|
17
|
-
raise "#{Photein::Config.
|
18
|
-
raise "#{Photein::Config.source}: no photos or videos found" if Dir.empty?(SRC_DIR)
|
13
|
+
raise "#{Photein::Config.source}: no such directory" unless Photein::Config.source.exist?
|
14
|
+
raise "#{Photein::Config.source}: no photos or videos found" if Dir.empty?(Photein::Config.source)
|
19
15
|
rescue => e
|
20
16
|
Photein.logger.fatal(e.message)
|
21
17
|
exit 1
|
@@ -24,7 +20,7 @@ end
|
|
24
20
|
# Cleanup ----------------------------------------------------------------------
|
25
21
|
at_exit do
|
26
22
|
unless Photein::Config.keep
|
27
|
-
Dir[
|
23
|
+
Dir[Photein::Config.source.join('**/')].sort
|
28
24
|
.drop(1)
|
29
25
|
.reverse_each { |d| Dir.rmdir(d) if Dir.empty?(d) }
|
30
26
|
end
|
data/lib/photein/config.rb
CHANGED
@@ -8,15 +8,16 @@ module Photein
|
|
8
8
|
include Singleton
|
9
9
|
|
10
10
|
OPTIONS = [
|
11
|
-
['-v', '--verbose',
|
12
|
-
['-s SOURCE', '--source=SOURCE',
|
13
|
-
['-
|
14
|
-
['-
|
15
|
-
['-
|
16
|
-
['-
|
17
|
-
['-
|
18
|
-
[
|
19
|
-
[
|
11
|
+
['-v', '--verbose', 'print verbose output'],
|
12
|
+
['-s SOURCE', '--source=SOURCE', 'path to the source directory'],
|
13
|
+
['-m MASTER', '--library-master=MASTER', 'path to a destination directory (master)'],
|
14
|
+
['-d DESKTOP', '--library-desktop=DESKTOP', 'path to a destination directory (desktop-optimized)'],
|
15
|
+
['-w WEB', '--library-web=WEB', 'path to a destination directory (web-optimized)'],
|
16
|
+
['-r', '--recursive', 'ingest source files recursively'],
|
17
|
+
['-k', '--keep', 'do not delete source files'],
|
18
|
+
['-i', '--interactive', 'ask whether to import each file found'],
|
19
|
+
['-n', '--dry-run', 'perform a "no-op" trial run'],
|
20
|
+
[ '--safe', 'skip files in use by other processes'],
|
20
21
|
].freeze
|
21
22
|
|
22
23
|
OPTION_NAMES = OPTIONS
|
@@ -46,7 +47,8 @@ module Photein
|
|
46
47
|
@params.freeze
|
47
48
|
|
48
49
|
raise "no source directory given" if !@params.key?(:source)
|
49
|
-
|
50
|
+
(%i[library-master library-desktop library-web] & @params.keys)
|
51
|
+
.then { |dest_dirs| raise "no destination directory given" if dest_dirs.empty? }
|
50
52
|
rescue => e
|
51
53
|
warn("#{parser.program_name}: #{e.message}")
|
52
54
|
warn(parser.help) if e.is_a?(OptionParser::ParseError)
|
@@ -65,6 +67,18 @@ module Photein
|
|
65
67
|
def respond_to_missing?(m, *args)
|
66
68
|
@params.key?(m.to_s.tr('_', '-').to_sym) || super
|
67
69
|
end
|
70
|
+
|
71
|
+
def source
|
72
|
+
@source ||= Pathname(@params[:source])
|
73
|
+
end
|
74
|
+
|
75
|
+
def destinations
|
76
|
+
@destinations ||= {
|
77
|
+
master: Pathname(@params[:'library-master']),
|
78
|
+
desktop: Pathname(@params[:'library-desktop']),
|
79
|
+
web: Pathname(@params[:'library-web'])
|
80
|
+
}.compact
|
81
|
+
end
|
68
82
|
end
|
69
83
|
end
|
70
84
|
end
|
data/lib/photein/image.rb
CHANGED
@@ -22,8 +22,8 @@ module Photein
|
|
22
22
|
}.freeze
|
23
23
|
MAX_RES_WEB = 2097152 # 2MP
|
24
24
|
|
25
|
-
def optimize
|
26
|
-
return
|
25
|
+
def optimize(tempfile:, lib_type:)
|
26
|
+
return unless lib_type == :web
|
27
27
|
|
28
28
|
case extname
|
29
29
|
when '.jpg', '.heic'
|
@@ -95,10 +95,8 @@ module Photein
|
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
98
|
-
def non_optimizable_format?
|
99
|
-
return
|
100
|
-
return false if Photein::Config.optimize_for == :desktop
|
101
|
-
return true if extname == '.dng'
|
98
|
+
def non_optimizable_format?(lib_type)
|
99
|
+
return true if lib_type == :web && extname == '.dng'
|
102
100
|
|
103
101
|
return false
|
104
102
|
end
|
data/lib/photein/media_file.rb
CHANGED
@@ -23,22 +23,38 @@ module Photein
|
|
23
23
|
return if corrupted?
|
24
24
|
return if Photein::Config.interactive && denied_by_user?
|
25
25
|
return if Photein::Config.safe && in_use?
|
26
|
-
return if Photein::Config.optimize_for && non_optimizable_format?
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
27
|
+
Photein::Config.destinations.map do |lib_type, lib_path|
|
28
|
+
next if non_optimizable_format?(lib_type)
|
29
|
+
|
30
|
+
Thread.new do
|
31
|
+
dest_basename = timestamp.strftime(DATE_FORMAT)
|
32
|
+
dest_extname = self.class::OPTIMIZATION_FORMAT_MAP.dig(lib_type, extname) || extname
|
33
|
+
dest_path = lib_path
|
34
|
+
.join(timestamp.strftime('%Y'))
|
35
|
+
.join("#{dest_basename}#{dest_extname}")
|
36
|
+
.then(&method(:resolve_name_collision))
|
37
|
+
tempfile = Pathname(Dir.tmpdir)
|
38
|
+
.join('photein').join(lib_type.to_s)
|
39
|
+
.tap(&FileUtils.method(:mkdir_p))
|
40
|
+
.join(dest_path.basename)
|
41
|
+
|
42
|
+
optimize(tempfile: tempfile, lib_type: lib_type)
|
43
|
+
|
44
|
+
Photein.logger.info(<<~MSG.chomp)
|
45
|
+
#{Photein::Config.keep ? 'copying' : 'moving'} #{path.basename} to #{dest_path}
|
46
|
+
MSG
|
47
|
+
|
48
|
+
FileUtils.mkdir_p(dest_path.dirname, noop: Photein::Config.dry_run)
|
49
|
+
|
50
|
+
if File.exist?(tempfile)
|
51
|
+
FileUtils.mv(tempfile, dest_path, noop: Photein::Config.dry_run)
|
52
|
+
else
|
53
|
+
FileUtils.cp(path, dest_path, noop: Photein::Config.dry_run)
|
54
|
+
FileUtils.chmod('-x', dest_path, noop: Photein::Config.dry_run)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end.each(&:join)
|
42
58
|
|
43
59
|
FileUtils.rm(path, noop: Photein::Config.dry_run || Photein::Config.keep)
|
44
60
|
end
|
@@ -68,29 +84,10 @@ module Photein
|
|
68
84
|
end
|
69
85
|
end
|
70
86
|
|
71
|
-
def non_optimizable_format? # may be overridden by subclasses
|
87
|
+
def non_optimizable_format?(lib_type = :master) # may be overridden by subclasses
|
72
88
|
return false
|
73
89
|
end
|
74
90
|
|
75
|
-
def parent_dir
|
76
|
-
Pathname(Photein::Config.dest).join(timestamp.strftime('%Y'))
|
77
|
-
end
|
78
|
-
|
79
|
-
def tempfile
|
80
|
-
Pathname(Dir.tmpdir).join('photein')
|
81
|
-
.tap(&FileUtils.method(:mkdir_p))
|
82
|
-
.join(dest_path.basename)
|
83
|
-
end
|
84
|
-
|
85
|
-
def dest_path
|
86
|
-
@dest_path ||= begin
|
87
|
-
base_path = parent_dir.join("#{timestamp.strftime(DATE_FORMAT)}#{dest_extname}")
|
88
|
-
counter = resolve_name_collision(base_path.sub_ext("*#{dest_extname}"))
|
89
|
-
|
90
|
-
base_path.sub_ext("#{counter}#{dest_extname}")
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
91
|
def timestamp
|
95
92
|
@timestamp ||= (metadata_stamp || filename_stamp)
|
96
93
|
end
|
@@ -105,16 +102,15 @@ module Photein
|
|
105
102
|
end
|
106
103
|
end
|
107
104
|
|
108
|
-
def dest_extname
|
109
|
-
self.class::OPTIMIZATION_FORMAT_MAP
|
110
|
-
.dig(Photein::Config.optimize_for, extname) || extname
|
111
|
-
end
|
112
|
-
|
113
105
|
def extname
|
114
106
|
@extname ||= NORMAL_EXTNAME_MAP[path.extname.downcase] || path.extname.downcase
|
115
107
|
end
|
116
108
|
|
117
|
-
def resolve_name_collision(
|
109
|
+
def resolve_name_collision(filename)
|
110
|
+
raise ArgumentError, 'Invalid filename' if filename.to_s.include?('*')
|
111
|
+
|
112
|
+
collision_glob = Pathname(filename).sub_ext("*#{filename.extname}")
|
113
|
+
|
118
114
|
case Dir[collision_glob].length
|
119
115
|
when 0 # if no files found, no biggie
|
120
116
|
when 1 # if one file found, WITH OR WITHOUT COUNTER, reset counter to a
|
@@ -125,9 +121,10 @@ module Photein
|
|
125
121
|
else # TODO: if multiple files found, rectify them?
|
126
122
|
end
|
127
123
|
|
128
|
-
# return the next usable
|
124
|
+
# return the next usable filename
|
129
125
|
Dir[collision_glob].max&.slice(/.(?=#{Regexp.escape(collision_glob.extname)})/)&.next
|
130
126
|
.tap { |counter| raise 'Unresolved timestamp conflict' unless [*Array('a'..'z'), nil].include?(counter) }
|
127
|
+
.then { |counter| filename.sub_ext("#{counter}#{filename.extname}") }
|
131
128
|
end
|
132
129
|
end
|
133
130
|
end
|
data/lib/photein/version.rb
CHANGED
data/lib/photein/video.rb
CHANGED
@@ -33,8 +33,9 @@ module Photein
|
|
33
33
|
web: '35',
|
34
34
|
}.freeze
|
35
35
|
|
36
|
-
def optimize
|
37
|
-
return if
|
36
|
+
def optimize(tempfile:, lib_type:)
|
37
|
+
return if lib_type == :master
|
38
|
+
return if video.bitrate < BITRATE_THRESHOLD[lib_type]
|
38
39
|
|
39
40
|
Photein.logger.info("transcoding #{tempfile}")
|
40
41
|
return if Photein::Config.dry_run
|
@@ -45,7 +46,7 @@ module Photein
|
|
45
46
|
'-map_metadata', '0', # https://video.stackexchange.com/a/26076
|
46
47
|
'-movflags', 'use_metadata_tags',
|
47
48
|
'-c:v', 'libx264',
|
48
|
-
'-crf', TARGET_CRF[
|
49
|
+
'-crf', TARGET_CRF[lib_type],
|
49
50
|
],
|
50
51
|
&method(:display_progress_bar)
|
51
52
|
)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: photein
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Lue
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-12-
|
11
|
+
date: 2021-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mediainfo
|