Skip to content

Commit

Permalink
Add classify by folder feature, keep the metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
yajw committed May 25, 2021
1 parent 8a3e3d4 commit 5c8424f
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 12 deletions.
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
}

group = "io.github.yajw"
version = "1.0-SNAPSHOT"
version = "1.1-SNAPSHOT"

repositories {
mavenCentral()
Expand All @@ -14,6 +14,7 @@ repositories {
dependencies {
implementation("com.jayway.jsonpath", "json-path", "2.5.0")
implementation("org.slf4j", "slf4j-simple", "1.7.30")
implementation("com.drewnoakes", "metadata-extractor", "2.16.0")
testImplementation(kotlin("test-junit"))
}

Expand Down
5 changes: 3 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ options:
2. `-c`: thread pool size
3. `-i`: input path
4. `-o`: output path

5. `-C`: write target image to yyyy/mm subfolder, the date uses image capture date
```text
> java -jar picc-1.0-SNAPSHOT.jar -i ~/pics/ -o ~/compressed/ -k key1,key2 -c 20
> java -jar picc-1.0-SNAPSHOT.jar -i ~/pics/ -o ~/compressed/ -k key1,key2 -c 20 -C true
done! 12 MB => 4 MB
```
65 changes: 56 additions & 9 deletions src/main/kotlin/main.kt
Original file line number Diff line number Diff line change
@@ -1,24 +1,46 @@
import com.drew.imaging.jpeg.JpegMetadataReader
import com.drew.metadata.Directory
import com.drew.metadata.exif.ExifIFD0Descriptor
import com.drew.metadata.exif.ExifIFD0Directory
import com.drew.metadata.exif.ExifSubIFDDirectory
import com.jayway.jsonpath.JsonPath
import java.io.File
import java.lang.Error
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.attribute.BasicFileAttributeView
import java.nio.file.attribute.BasicFileAttributes
import java.time.Duration
import java.time.ZoneId
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicLong


val httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(2)).build()

fun main(args: Array<String>) {
val opts = args.toList().zipWithNext().toMap()
compress(opts["-k"]?.split(","), opts["-i"], opts["-o"], opts["-c"]?.toInt() ?: 100)
compress(
opts["-k"]?.split(","),
opts["-i"],
opts["-o"],
opts["-c"]?.toInt() ?: 100,
opts["-C"].toBoolean(),
)
}

fun compress(keys: List<String>?, inputPath: String?, outputPath: String?, threadPoolSize: Int) {
fun compress(
keys: List<String>?,
inputPath: String?,
outputPath: String?,
threadPoolSize: Int,
classifyByDate: Boolean
) {
if (keys.isNullOrEmpty()) {
System.err.println("key can't be null")
return
Expand All @@ -43,7 +65,7 @@ fun compress(keys: List<String>?, inputPath: String?, outputPath: String?, threa
executor.submit {
try {
originalSize.addAndGet(Files.size(it.toPath()))
val size = compressOne(keys.random(), it, outputPath)
val size = compressOne(keys.random(), it, outputPath, classifyByDate)
outputSize.addAndGet(size)
} catch (e: Exception) {
println(it.absolutePath)
Expand All @@ -57,10 +79,22 @@ fun compress(keys: List<String>?, inputPath: String?, outputPath: String?, threa
}


fun compressOne(key: String, inputFile: File, outputPath: String): Long {
val targetFilePath = Paths.get(outputPath, "c_${inputFile.name}").toString()
fun compressOne(key: String, inputFile: File, outputPath: String, classifyByDate: Boolean): Long {
var targetFilePath = Paths.get(outputPath, "c_${inputFile.name}").toString()
val attrs = Files.readAttributes(inputFile.toPath(), BasicFileAttributes::class.java)
val captureDate = getCaptureDate(inputFile)
if (classifyByDate && captureDate != null) {
try {
val localDate = captureDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()
val folder = Paths.get(outputPath, localDate.year.toString(), localDate.month.value.toString().padStart(2, '0'))
Files.createDirectories(folder)
targetFilePath = Paths.get(folder.toString(), "c_${inputFile.name}").toString()
} catch (e: Exception) {
e.printStackTrace()
}
}
return shrink(key, inputFile.absolutePath) {
copy(key, it, targetFilePath)
copy(key, it, targetFilePath, attrs)
}
}

Expand All @@ -77,17 +111,30 @@ fun shrink(key: String, path: String, cb: (location: String) -> Long): Long {
return size
}

fun copy(key: String, location: String, targetPath: String): Long {
fun copy(key: String, location: String, targetPath: String, attrs: BasicFileAttributes): Long {
val path = Paths.get(targetPath)
val request = HttpRequest.newBuilder()
.uri(URI.create(location))
.headers("Authorization", buildAuth(key))
.GET()
.header("Authorization", buildAuth(key))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"preserve\":[\"copyright\",\"creation\",\"location\"]}"))
.build()
httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofFile(path)).join()
val targetFileAttrs = Files.getFileAttributeView(path, BasicFileAttributeView::class.java)
targetFileAttrs.setTimes(attrs.lastModifiedTime(), attrs.lastAccessTime(), attrs.creationTime())
return Files.size(path)
}

fun buildAuth(key: String): String {
return "Basic ${Base64.getEncoder().encodeToString("api:$key".toByteArray())}"
}

fun getCaptureDate(jpegFile: File): Date? {
try {
val metadata = JpegMetadataReader.readMetadata(jpegFile)
val d = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory::class.java)
return d.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL)
} catch (e: Exception) {
return null
}
}

0 comments on commit 5c8424f

Please sign in to comment.