photein 0.0.1 β 0.0.6
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 +100 -77
- data/lib/photein/image.rb +17 -3
- data/lib/photein/media_file.rb +7 -0
- 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: 5404ec50b0c5a57144fea72eff5639ca90a19a46be319cb8ee634d9f67291617
|
4
|
+
data.tar.gz: bce672716a27ea28f9d4203e1858affa8793a11b5ca94e3642c6d86399969680
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f11aba53e3bcdef67a150a549da7c16c45e0350b4bbdcfb4f1d1ce4c0fd778185845ef4719857e9eea4b1c15894578ae4e1b361952da3961c77fb1b0cc56c0bb
|
7
|
+
data.tar.gz: 2967ccf17b7d7631325ff9b18e445b052a52db8ddec3043cc0b4b15945b0b0c0bba52f0862044f554067950b578447235771abd1d97103346492ba6de821029a
|
data/README.md
CHANGED
@@ -1,115 +1,138 @@
|
|
1
1
|
PhπΈtein
|
2
2
|
========
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
Why?
|
7
|
-
----
|
8
|
-
|
9
|
-
* The major cloud photo services (iCloud, Google Photos) are great but not FOSS.
|
10
|
-
|
11
|
-
(My digital photo/video library belongs to me,
|
12
|
-
but if I donβt control the pipeline for viewing/managing it,
|
13
|
-
then does it really?)
|
14
|
-
|
15
|
-
* Importing photos from many sources
|
16
|
-
(π± cell phone / π· digital camera / π¬ chat app)
|
17
|
-
into one library with as few manual steps as possible
|
18
|
-
is not simple, especially on Linux.
|
4
|
+
A no-nonsense way to organize your personal photo library.
|
19
5
|
|
20
6
|
What does it do?
|
21
7
|
----------------
|
22
8
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
* EXIF metadata
|
28
|
-
* filename
|
29
|
-
* file birthtime
|
30
|
-
* rename files by that timestamp (`YYYY-mm-dd_HHMMSS.jpg`)
|
31
|
-
* handle conflicts for identical timestamps (`YYYY-mm-dd_HHMMSSa.jpg`, `YYYY-mm-dd_HHMMSSb.jpg`...)
|
32
|
-
* sort files into subdirectories by year (`YYYY/YYYY-mm-dd_HHMMSS.jpg`)
|
33
|
-
* optionally, optimize media for reduced filesize
|
34
|
-
|
35
|
-
#### Supported media formats
|
9
|
+
Photein manages your photos **at the filesystem level**.
|
10
|
+
It wonβt let you browse or edit your photos,
|
11
|
+
but it will give them a uniform folder structure and filenames,
|
12
|
+
no matter where they come from:
|
36
13
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
14
|
+
```sh
|
15
|
+
# Before # After
|
16
|
+
|
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
|
33
|
+
```
|
43
34
|
|
44
|
-
|
35
|
+
Photein generates these folders & filenames
|
36
|
+
based on metadata timestamps, filename timestamps, or file creation times.
|
45
37
|
|
46
|
-
|
38
|
+
> β οΈ **Note**
|
39
|
+
>
|
40
|
+
> If you use a photo management app that decides
|
41
|
+
> where and how your photos should be stored on disk
|
42
|
+
> (looking at you, Apple Photos π), Photein is not for you.
|
47
43
|
|
48
|
-
|
49
|
-
to mount, import from, and unmount your camera
|
50
|
-
whenever you plug it in via USB.
|
44
|
+
It can also optimize photos and videos for reduced file size.
|
51
45
|
|
52
|
-
|
53
|
-
|
54
|
-
$ curl https://raw.githubusercontent.com/rlue/photein/master/examples/share/systemd/user/photein-dcim.service -o ~/.local/share/systemd/user/photein-dcim.service
|
55
|
-
$ systemctl --user daemon-reload
|
56
|
-
$ systemctl --user enable photein-dcim.service
|
57
|
-
```
|
46
|
+
What _doesnβt_ it do?
|
47
|
+
---------------------
|
58
48
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
> * Your deviceβs label is `RICOH_GR`.
|
63
|
-
> (Use `systemctl --all --full -t device`
|
64
|
-
> to determine the label of your USB device.)
|
65
|
-
> * You use [rbenv][] to manage your systemβs Ruby environment.
|
66
|
-
>
|
67
|
-
> Adjust accordingly.
|
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.
|
68
52
|
|
69
|
-
|
70
|
-
[rbenv]: https://github.com/rbenv/rbenv
|
53
|
+
If you want to:
|
71
54
|
|
72
|
-
|
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
|
73
58
|
|
74
|
-
|
75
|
-
|
76
|
-
your library on a daily basis.
|
59
|
+
check out Photeinβs sister utility [Xferase][],
|
60
|
+
or try the [automation guides][] below.
|
77
61
|
|
78
|
-
[
|
62
|
+
[Xferase]: https://github.com/rlue/xferase
|
63
|
+
[automation guides]: #automation-guides
|
79
64
|
|
80
65
|
Installation
|
81
66
|
------------
|
82
67
|
|
83
68
|
```sh
|
84
|
-
$
|
85
|
-
$ cd photein
|
86
|
-
$ gem build photein.gemspec
|
87
|
-
$ gem install photein-0.0.1.gem
|
69
|
+
$ gem install photein
|
88
70
|
```
|
89
71
|
|
72
|
+
### Dependencies
|
73
|
+
|
74
|
+
* Ruby 2.6+
|
75
|
+
* [ExifTool][]
|
76
|
+
* [MediaInfo][]
|
77
|
+
* ImageMagick (for `--optimize-for=web` option)
|
78
|
+
* OptiPNG (for `--optimize-for=web` option)
|
79
|
+
* ffmpeg (for `--optimize-for={web,desktop}` options)
|
80
|
+
|
81
|
+
[ExifTool]: https://exiftool.org/
|
82
|
+
[MediaInfo]: https://mediaarea.net/MediaInfo
|
83
|
+
|
90
84
|
Usage
|
91
85
|
-----
|
92
86
|
|
93
87
|
```sh
|
94
88
|
$ photein \
|
95
|
-
--source
|
96
|
-
--
|
89
|
+
--source /media/ricoh_gr/DCIM \ # batch-import photos from here
|
90
|
+
--recursive \ # including subdirectories
|
91
|
+
--dest /home/rlue/Pictures # into here
|
97
92
|
```
|
98
93
|
|
99
94
|
Use `photein --help` for a summary of all options.
|
100
95
|
|
101
|
-
|
102
|
-
------------
|
96
|
+
#### Supported media formats
|
103
97
|
|
104
|
-
*
|
105
|
-
*
|
106
|
-
*
|
107
|
-
*
|
108
|
-
*
|
109
|
-
*
|
98
|
+
* .jpg
|
99
|
+
* .dng
|
100
|
+
* .heic
|
101
|
+
* .png
|
102
|
+
* .mp4
|
103
|
+
* .mov
|
110
104
|
|
111
|
-
|
112
|
-
|
105
|
+
### Automation guides
|
106
|
+
|
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.
|
118
|
+
|
119
|
+
Development
|
120
|
+
-----------
|
121
|
+
|
122
|
+
Contributions welcome.
|
123
|
+
|
124
|
+
> β οΈ **Warning**
|
125
|
+
>
|
126
|
+
> The RSpec test suite contains no unit tests.
|
127
|
+
> It solely tests `photein` as a CLI utility, or in other words,
|
128
|
+
> it defines expectations against the effects of `system('photein <args>')`.
|
129
|
+
>
|
130
|
+
> Because `Kernel#system` runs the given command in a subprocess,
|
131
|
+
> it prints to a different stdout than `rspec` itself.
|
132
|
+
> This makes test failures cumbersome to debug,
|
133
|
+
> because `puts` statements never appear in the test output,
|
134
|
+
> and `binding.pry` will cause the test to appear to hang
|
135
|
+
> as it waits for user input on an invisible stdin.
|
113
136
|
|
114
137
|
License
|
115
138
|
-------
|
data/lib/photein/image.rb
CHANGED
@@ -41,10 +41,14 @@ module Photein
|
|
41
41
|
convert << tempfile
|
42
42
|
end unless Photein::Config.dry_run
|
43
43
|
when '.png'
|
44
|
-
return if !Optipng.available?
|
45
|
-
|
46
44
|
FileUtils.cp(path, tempfile, noop: Photein::Config.dry_run)
|
47
|
-
|
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
|
48
52
|
end
|
49
53
|
end
|
50
54
|
|
@@ -52,10 +56,20 @@ module Photein
|
|
52
56
|
|
53
57
|
def image
|
54
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
|
55
64
|
end
|
56
65
|
|
57
66
|
def metadata_stamp
|
58
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
|
59
73
|
end
|
60
74
|
|
61
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 }
|
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.6
|
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-07-23 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
|