photein 0.0.5 → 0.0.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2e520a4d9508ba94604273c88b9f0d3bb1052aae63cef56c856595e5b0b3866
4
- data.tar.gz: 14633d71731ff23c3350c861bfe33d289bfb53777d6369a6245c203de93dacde
3
+ metadata.gz: 7b3bcb770e66b470a49fd5f486560c447e92ba8df6daa33434c45fdd3564a2e7
4
+ data.tar.gz: 83df115282d7cef28bfd2f32a42643a843726ea40c8eda1de5e7908f1b7f4832
5
5
  SHA512:
6
- metadata.gz: cf796a670cc45ce45238b4e9b0e23641ce1dc21aa67008bb016e9507b0470bebdd4790620bfd5325eabdcbe833165588c32811b244b5f8271ff8fa05d5b2d473
7
- data.tar.gz: deaea8ad0c1ad541c5e160ea927dbfef115902a7f25b96205e67c780e45477a0def030fe1dc4ba2063cc3c936fb7298ccb9f7f4dca70069e4e7ac4ec7fdd586f
6
+ metadata.gz: 33ceaea508cbe94cc40906364a1094c825c647e0bc7a38a1f9bdc21f1624a63a8b0c7e39351a0dcbc973afd2969af0e5e20615f3404d2b7b2af2f2cc6db1930f
7
+ data.tar.gz: 798e0351b09bcb1cbe0cae6875b6731cbfeeffb2cba1d2e09bc6b5e9ded21ebf009af302e2c17841e5b6cf2f025e5b1cbcd819787ce645205c3167e6afbb6ba7
data/README.md CHANGED
@@ -14,21 +14,22 @@ no matter where they come from:
14
14
  ```sh
15
15
  # Before # After
16
16
 
17
- ~/Pictures ~/Pictures
18
- └── _inbox ├── _inbox
19
- ├── 1619593208911.jpeg ├── 2020
20
- ├── DCIM├── 2020-08-01_113129.heic
21
- │ └── 2021_03_26 └── 2020-05-20_160209.png
22
- │ ├── R0014285.MOV └── 2021
23
- │ ├── R0014286.DNG ├── 2021-02-12_081933a.jpg
24
- │ ├── R0014286.JPG ├── 2021-02-12_081933b.jpg
25
- │ ├── R0014287.DNG ├── 2021-02-12_081939.mp4
26
- │ └── R0014287.JPG ├── 2021-03-26_161245.mp4
27
- ├── IMG_20210212_081933_001.jpg ├── 2021-03-26_161518.dng
28
- ├── IMG_20210212_081933_002.jpg ├── 2021-03-26_161518.jpg
29
- ├── IMG_8953.HEIC ├── 2021-03-26_170304.dng
30
- ├── Screenshot_20200520_160209.png ├── 2021-03-26_170304.jpg
31
- └── VID_20210212_081939.mp4 └── 2021-04-28_000008.jpg
17
+ ~ ~
18
+ ├── Downloads ├── Downloads
19
+ ├── 1619593208911.jpeg └── Pictures
20
+ ├── DCIM ├── 2020
21
+ └── 2021_03_26 ├── 2020-08-01_113129.heic
22
+ ├── R0014285.MOV└── 2020-05-20_160209.png
23
+ ├── R0014286.DNG └── 2021
24
+ ├── R0014286.JPG ├── 2021-02-12_081933a.jpg
25
+ ├── R0014287.DNG ├── 2021-02-12_081933b.jpg
26
+ └── R0014287.JPG ├── 2021-02-12_081939.mp4
27
+ ├── IMG_20210212_081933_001.jpg ├── 2021-03-26_161245.mp4
28
+ ├── IMG_20210212_081933_002.jpg ├── 2021-03-26_161518.dng
29
+ ├── IMG_8953.HEIC ├── 2021-03-26_161518.jpg
30
+ ├── Screenshot_20200520_160209.png ├── 2021-03-26_170304.dng
31
+ └── VID_20210212_081939.mp4 ├── 2021-03-26_170304.jpg
32
+ └── Pictures └── 2021-04-28_000008.jpg
32
33
  ```
33
34
 
34
35
  Photein generates these folders & filenames
@@ -38,7 +39,7 @@ based on metadata timestamps, filename timestamps, or file creation times.
38
39
  >
39
40
  > If you use a photo management app that decides
40
41
  > where and how your photos should be stored on disk
41
- > (👀 looking at you, Apple Photos), Photein is not for you.
42
+ > (looking at you, Apple Photos 👀), Photein is not for you.
42
43
 
43
44
  It can also optimize photos and videos for reduced file size.
44
45
 
data/bin/photein CHANGED
@@ -5,7 +5,7 @@ require 'photein'
5
5
  require 'pathname'
6
6
 
7
7
  Photein::Config.parse_opts!
8
- Photein::Logger.open
8
+ Photein.logger.open
9
9
 
10
10
  # Setup ------------------------------------------------------------------------
11
11
 
@@ -17,7 +17,7 @@ begin
17
17
  raise "#{Photein::Config.dest}: no such directory" unless DEST_DIR.exist?
18
18
  raise "#{Photein::Config.source}: no photos or videos found" if Dir.empty?(SRC_DIR)
19
19
  rescue => e
20
- Photein::Logger.fatal(e.message)
20
+ Photein.logger.fatal(e.message)
21
21
  exit 1
22
22
  end
23
23
 
@@ -33,25 +33,4 @@ at_exit do
33
33
  end
34
34
 
35
35
  # Core Logic -------------------------------------------------------------------
36
- image_formats = Photein::Image::SUPPORTED_FORMATS
37
- .zip(Photein::Image::SUPPORTED_FORMATS.map(&:upcase))
38
- .flatten
39
-
40
- SRC_DIR
41
- .join(Photein::Config.recursive ? '**' : '')
42
- .join("*{#{image_formats.join(',')}}")
43
- .then { |glob| Dir[glob].sort }
44
- .map(&Photein::Image.method(:new))
45
- .each(&:import)
46
-
47
- # Video compression is time-consuming, so save it for last
48
- video_formats = Photein::Video::SUPPORTED_FORMATS
49
- .zip(Photein::Video::SUPPORTED_FORMATS.map(&:upcase))
50
- .flatten
51
-
52
- SRC_DIR
53
- .join(Photein::Config.recursive ? '**' : '')
54
- .join("*{#{video_formats.join(',')}}")
55
- .then { |glob| Dir[glob].sort }
56
- .map(&Photein::Video.method(:new))
57
- .each(&:import)
36
+ Photein.run
@@ -25,10 +25,14 @@ module Photein
25
25
  .map { |option| option[/\w[a-z\-]+/] }
26
26
  .map(&:to_sym)
27
27
 
28
+ @params = {}
29
+
28
30
  class << self
29
- def parse_opts!
30
- @params = {}
31
+ def set(**params)
32
+ @params.replace(params)
33
+ end
31
34
 
35
+ def parse_opts!
32
36
  parser = OptionParser.new do |opts|
33
37
  opts.version = Photein::VERSION
34
38
  opts.banner = <<~BANNER
data/lib/photein/image.rb CHANGED
@@ -29,7 +29,7 @@ module Photein
29
29
  when '.jpg', '.heic'
30
30
  return false if image.dimensions.reduce(&:*) < MAX_RES_WEB
31
31
 
32
- Photein::Logger.info "optimizing #{path}"
32
+ Photein.logger.info "optimizing #{path}"
33
33
  MiniMagick::Tool::Convert.new do |convert|
34
34
  convert << path
35
35
  convert.colorspace('sRGB')
@@ -42,8 +42,13 @@ module Photein
42
42
  end unless Photein::Config.dry_run
43
43
  when '.png'
44
44
  FileUtils.cp(path, tempfile, noop: Photein::Config.dry_run)
45
- Photein::Logger.info "optimizing #{path}"
46
- Optipng.optimize(tempfile, level: 4) unless Photein::Config.dry_run
45
+ Photein.logger.info "optimizing #{path}"
46
+ begin
47
+ Optipng.optimize(tempfile, level: 4) unless Photein::Config.dry_run
48
+ rescue Errno::ENOENT
49
+ Photein.logger.error('optipng is required to compress PNG images')
50
+ raise
51
+ end
47
52
  end
48
53
  end
49
54
 
@@ -51,10 +56,20 @@ module Photein
51
56
 
52
57
  def image
53
58
  @image ||= MiniMagick::Image.open(path)
59
+ rescue MiniMagick::Invalid => e
60
+ Photein.logger.error(<<~MSG) if e.message.match?(/You must have ImageMagick/)
61
+ ImageMagick is required to manipulate image files
62
+ MSG
63
+ raise
54
64
  end
55
65
 
56
66
  def metadata_stamp
57
67
  MiniExiftool.new(path.to_s).date_time_original
68
+ rescue MiniExiftool::Error => e
69
+ Photein.logger.error(<<~MSG) if e.message.match?(/exiftool: not found/)
70
+ exiftool is required to read timestamp metadata
71
+ MSG
72
+ raise
58
73
  end
59
74
 
60
75
  # NOTE: This may be largely unnecessary:
@@ -4,6 +4,14 @@ require 'logger'
4
4
  require 'singleton'
5
5
 
6
6
  module Photein
7
+ class << self
8
+ attr_writer :logger
9
+
10
+ def logger
11
+ @logger ||= Photein::Logger
12
+ end
13
+ end
14
+
7
15
  class Logger
8
16
  include Singleton
9
17
 
@@ -20,6 +20,7 @@ module Photein
20
20
  end
21
21
 
22
22
  def import
23
+ return if corrupted?
23
24
  return if Photein::Config.interactive && denied_by_user?
24
25
  return if Photein::Config.safe && in_use?
25
26
  return if Photein::Config.optimize_for && non_optimizable_format?
@@ -28,7 +29,7 @@ module Photein
28
29
 
29
30
  optimize if Photein::Config.optimize_for
30
31
 
31
- Photein::Logger.info(<<~MSG.chomp)
32
+ Photein.logger.info(<<~MSG.chomp)
32
33
  #{Photein::Config.keep ? 'copying' : 'moving'} #{path.basename} to #{dest_path}
33
34
  MSG
34
35
 
@@ -44,6 +45,12 @@ module Photein
44
45
 
45
46
  private
46
47
 
48
+ def corrupted?(result = false)
49
+ return result.tap do |r|
50
+ Photein.logger.error("#{path.basename}: cannot import corrupted file") if r
51
+ end
52
+ end
53
+
47
54
  def denied_by_user?
48
55
  $stdout.printf "Import #{path}? [y/N]"
49
56
  (STDIN.getch.downcase != 'y').tap { $stdout.puts }
@@ -54,7 +61,7 @@ module Photein
54
61
 
55
62
  if status.success? # Do open files ALWAYS return exit status 0? (I think so.)
56
63
  cmd, pid = out.lines[1]&.split&.first(2)
57
- Photein::Logger.fatal("skipping #{path}: file in use by #{cmd} (PID #{pid})")
64
+ Photein.logger.fatal("skipping #{path}: file in use by #{cmd} (PID #{pid})")
58
65
  return true
59
66
  else
60
67
  return false
@@ -112,8 +119,8 @@ module Photein
112
119
  when 0 # if no files found, no biggie
113
120
  when 1 # if one file found, WITH OR WITHOUT COUNTER, reset counter to a
114
121
  if Dir[collision_glob].first != collision_glob.sub('*', 'a') # don't try if it's already a lone, correctly-countered file
115
- Photein::Logger.info('conflicting timestamp found; adding counter to existing file')
116
- FileUtils.mv(Dir[collision_glob].first, collision_glob.sub('*', 'a'))
122
+ Photein.logger.info('conflicting timestamp found; adding counter to existing file')
123
+ FileUtils.mv(Dir[collision_glob].first, collision_glob.sub('*', 'a'), noop: Photein::Config.dry_run)
117
124
  end
118
125
  else # TODO: if multiple files found, rectify them?
119
126
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Photein
4
- VERSION = '0.0.5'
4
+ VERSION = '0.0.9'
5
5
  end
data/lib/photein/video.rb CHANGED
@@ -36,7 +36,7 @@ module Photein
36
36
  def optimize
37
37
  return if video.bitrate < BITRATE_THRESHOLD[Photein::Config.optimize_for]
38
38
 
39
- Photein::Logger.info("transcoding #{tempfile}")
39
+ Photein.logger.info("transcoding #{tempfile}")
40
40
  return if Photein::Config.dry_run
41
41
 
42
42
  video.transcode(
@@ -53,13 +53,23 @@ module Photein
53
53
 
54
54
  private
55
55
 
56
+ def corrupted?
57
+ super(video.bitrate.nil?)
58
+ end
59
+
56
60
  def video
57
61
  @video ||= FFMPEG::Movie.new(path.to_s)
62
+ rescue Errno::ENOENT
63
+ Photein.logger.error('ffmpeg is required to manipulate video files')
64
+ raise
58
65
  end
59
66
 
60
67
  def metadata_stamp
61
68
  # video timestamps are typically UTC
62
69
  MediaInfo.from(path.to_s).general.encoded_date&.getlocal
70
+ rescue MediaInfo::EnvironmentError
71
+ Photein.logger.error('mediainfo is required to read timestamp metadata')
72
+ raise
63
73
  end
64
74
 
65
75
  # NOTE: This may be largely unnecessary:
data/lib/photein.rb CHANGED
@@ -8,4 +8,16 @@ require 'photein/image'
8
8
  require 'photein/video'
9
9
 
10
10
  module Photein
11
+ class << self
12
+ def run
13
+ [Photein::Image, Photein::Video].each do |media_type|
14
+ Pathname(Photein::Config.source)
15
+ .join(Photein::Config.recursive ? '**' : '')
16
+ .join("*{#{media_type::SUPPORTED_FORMATS.join(',')}}")
17
+ .then { |glob| Dir.glob(glob, File::FNM_CASEFOLD).sort }
18
+ .map(&media_type.method(:new))
19
+ .each(&:import)
20
+ end
21
+ end
22
+ end
11
23
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: photein
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.9
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-05-28 00:00:00.000000000 Z
11
+ date: 2021-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: debouncer
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '0.2'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '0.2'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: mediainfo
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -94,20 +80,6 @@ dependencies:
94
80
  - - "~>"
95
81
  - !ruby/object:Gem::Version
96
82
  version: '0.2'
97
- - !ruby/object:Gem::Dependency
98
- name: rb-inotify
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '0.10'
104
- type: :runtime
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '0.10'
111
83
  - !ruby/object:Gem::Dependency
112
84
  name: streamio-ffmpeg
113
85
  requirement: !ruby/object:Gem::Requirement
@@ -123,19 +95,19 @@ dependencies:
123
95
  - !ruby/object:Gem::Version
124
96
  version: '3.0'
125
97
  - !ruby/object:Gem::Dependency
126
- name: pry
98
+ name: pry-remote
127
99
  requirement: !ruby/object:Gem::Requirement
128
100
  requirements:
129
101
  - - "~>"
130
102
  - !ruby/object:Gem::Version
131
- version: '0.14'
103
+ version: '0.1'
132
104
  type: :development
133
105
  prerelease: false
134
106
  version_requirements: !ruby/object:Gem::Requirement
135
107
  requirements:
136
108
  - - "~>"
137
109
  - !ruby/object:Gem::Version
138
- version: '0.14'
110
+ version: '0.1'
139
111
  - !ruby/object:Gem::Dependency
140
112
  name: rspec
141
113
  requirement: !ruby/object:Gem::Requirement