photein 0.0.2 β 0.0.7
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/README.md +50 -69
- data/lib/photein/config.rb +6 -2
- data/lib/photein/image.rb +16 -1
- data/lib/photein/media_file.rb +8 -1
- data/lib/photein/version.rb +1 -1
- data/lib/photein/video.rb +10 -0
- metadata +11 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d67c47b483c2b3525960fa4aa80221a2143b7b4bf92d67e74622ffe3ecbcf40f
|
4
|
+
data.tar.gz: 215db5531f34667eab5bd8524a51c9497697e6bb56acf8ccac51acca94aa19ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 356351ee9bdb21340d47b57c12bc11857e774f8a81741f4aa2567b60555d8adc290daad8ddaea9a3ee9d48197b0a3e923fd6e2f6eaea67ef00ca51112e5895b1
|
7
|
+
data.tar.gz: f24b64e2a9faafd6ba95e85b7c3e198bc526e4f8dc216fc71e1dfab9ea3e1affacac3b8ce472b880202b13899abb98eb758504c06045380aa4e0c0ceeaa0b293
|
data/README.md
CHANGED
@@ -1,16 +1,12 @@
|
|
1
1
|
PhπΈtein
|
2
2
|
========
|
3
3
|
|
4
|
-
|
4
|
+
A no-nonsense way to organize your personal photo library.
|
5
5
|
|
6
6
|
What does it do?
|
7
7
|
----------------
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
### Photein
|
12
|
-
|
13
|
-
Photein is a CLI utility for managing your photos **at the filesystem level**.
|
9
|
+
Photein manages your photos **at the filesystem level**.
|
14
10
|
It wonβt let you browse or edit your photos,
|
15
11
|
but it will give them a uniform folder structure and filenames,
|
16
12
|
no matter where they come from:
|
@@ -18,73 +14,53 @@ no matter where they come from:
|
|
18
14
|
```sh
|
19
15
|
# Before # After
|
20
16
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
36
33
|
```
|
37
34
|
|
35
|
+
Photein generates these folders & filenames
|
36
|
+
based on metadata timestamps, filename timestamps, or file creation times.
|
37
|
+
|
38
38
|
> β οΈ **Note**
|
39
39
|
>
|
40
40
|
> If you use a photo management app that decides
|
41
|
-
> where and how your photos should be stored on
|
42
|
-
> Photein is not for you.
|
43
|
-
|
44
|
-
### Xferase
|
45
|
-
|
46
|
-
Xferase is a background service built on top of photein.
|
47
|
-
It watches a directory of your choosing,
|
48
|
-
and whenever any files are placed there,
|
49
|
-
it automatically imports them into your photo library.
|
50
|
-
|
51
|
-
It creates and manages two parallel copies of your library
|
52
|
-
(one original/hi-res, one optimized for web)
|
53
|
-
and ensures that when you delete a photo from one,
|
54
|
-
it is automatically removed from the other.
|
55
|
-
|
56
|
-
When combined with other software,
|
57
|
-
Xferase can be used as a self-hosted / DIY alternative
|
58
|
-
to cloud photo services like Google Photos or iCloud.
|
59
|
-
|
60
|
-
Why?
|
61
|
-
----
|
62
|
-
|
63
|
-
I could not find any existing software product that:
|
41
|
+
> where and how your photos should be stored on disk
|
42
|
+
> (looking at you, Apple Photos π), Photein is not for you.
|
64
43
|
|
65
|
-
|
44
|
+
It can also optimize photos and videos for reduced file size.
|
66
45
|
|
67
|
-
|
46
|
+
What _doesnβt_ it do?
|
47
|
+
---------------------
|
68
48
|
|
69
|
-
|
49
|
+
On its own, Photein is **not** an alternative
|
50
|
+
to cloud photo services like Google Photos or iCloudβbut
|
51
|
+
in combination with other software, it can be.
|
70
52
|
|
71
|
-
|
72
|
-
find them in an βOpen...β dialog,
|
73
|
-
or sync them to other devices with tools like Dropbox or Syncthing.)
|
53
|
+
If you want to:
|
74
54
|
|
75
|
-
|
55
|
+
* import photos from your phone as soon as you take them
|
56
|
+
* import photos from a digital camera / SD card as soon as you plug it in
|
57
|
+
* mirror a low-res copy of your entire photo library to your Android phone
|
76
58
|
|
77
|
-
|
78
|
-
|
79
|
-
then does it really?)
|
59
|
+
check out Photeinβs sister utility [Xferase][],
|
60
|
+
or try the [automation guides][] below.
|
80
61
|
|
81
|
-
|
82
|
-
|
83
|
-
> β οΈ **Note**
|
84
|
-
>
|
85
|
-
> Strictly speaking, Photein does not handle requirement #1;
|
86
|
-
> for that, use Xferase in combination with other software,
|
87
|
-
> such as Syncthing or systemd.
|
62
|
+
[Xferase]: https://github.com/rlue/xferase
|
63
|
+
[automation guides]: #automation-guides
|
88
64
|
|
89
65
|
Installation
|
90
66
|
------------
|
@@ -95,7 +71,7 @@ $ gem install photein
|
|
95
71
|
|
96
72
|
### Dependencies
|
97
73
|
|
98
|
-
* Ruby 2.
|
74
|
+
* Ruby 2.6+
|
99
75
|
* [ExifTool][]
|
100
76
|
* [MediaInfo][]
|
101
77
|
* ImageMagick (for `--optimize-for=web` option)
|
@@ -108,8 +84,6 @@ $ gem install photein
|
|
108
84
|
Usage
|
109
85
|
-----
|
110
86
|
|
111
|
-
### Simple import
|
112
|
-
|
113
87
|
```sh
|
114
88
|
$ photein \
|
115
89
|
--source /media/ricoh_gr/DCIM \ # batch-import photos from here
|
@@ -130,9 +104,17 @@ Use `photein --help` for a summary of all options.
|
|
130
104
|
|
131
105
|
### Automation guides
|
132
106
|
|
133
|
-
|
134
|
-
|
135
|
-
* [
|
107
|
+
Using Photein + systemd, you can:
|
108
|
+
|
109
|
+
* [π·β‘οΈπ₯οΈ Set up auto-import from a digital camera](guides/auto-import-digital-camera.md)
|
110
|
+
|
111
|
+
But for more complex tasks, like:
|
112
|
+
|
113
|
+
* π±β‘οΈπ₯οΈ Setting up auto-import from an Android phone
|
114
|
+
* π±ππ₯οΈ Mirroring your library across multiple devices
|
115
|
+
|
116
|
+
check out the documentation for [Xferase][],
|
117
|
+
an always-on background service based on Photein.
|
136
118
|
|
137
119
|
Development
|
138
120
|
-----------
|
@@ -146,8 +128,7 @@ Contributions welcome.
|
|
146
128
|
> it defines expectations against the effects of `system('photein <args>')`.
|
147
129
|
>
|
148
130
|
> Because `Kernel#system` runs the given command in a subprocess,
|
149
|
-
> it prints to a different stdout than
|
150
|
-
> native Ruby code in a normal RSpec example.
|
131
|
+
> it prints to a different stdout than `rspec` itself.
|
151
132
|
> This makes test failures cumbersome to debug,
|
152
133
|
> because `puts` statements never appear in the test output,
|
153
134
|
> and `binding.pry` will cause the test to appear to hang
|
data/lib/photein/config.rb
CHANGED
@@ -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
|
30
|
-
@params
|
31
|
+
def set(**params)
|
32
|
+
@params.replace(params).freeze
|
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
@@ -43,7 +43,12 @@ module Photein
|
|
43
43
|
when '.png'
|
44
44
|
FileUtils.cp(path, tempfile, noop: Photein::Config.dry_run)
|
45
45
|
Photein::Logger.info "optimizing #{path}"
|
46
|
-
|
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:
|
data/lib/photein/media_file.rb
CHANGED
@@ -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?
|
@@ -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 }
|
@@ -113,7 +120,7 @@ module Photein
|
|
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
122
|
Photein::Logger.info('conflicting timestamp found; adding counter to existing file')
|
116
|
-
FileUtils.mv(Dir[collision_glob].first, collision_glob.sub('*', 'a'))
|
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
|
data/lib/photein/version.rb
CHANGED
data/lib/photein/video.rb
CHANGED
@@ -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:
|
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.0.7
|
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-
|
11
|
+
date: 2021-12-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mediainfo
|
@@ -53,33 +53,33 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '4.11'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: nokogiri
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '1.11'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '1.11'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: optipng
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '0.2'
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '0.2'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: streamio-ffmpeg
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,19 +95,19 @@ dependencies:
|
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '3.0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name: pry
|
98
|
+
name: pry-remote
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: '0.
|
103
|
+
version: '0.1'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: '0.
|
110
|
+
version: '0.1'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: rspec
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|