Skip to content

Instantly share code, notes, and snippets.

@ivmos
Created May 16, 2020 10:27
Show Gist options
  • Save ivmos/860b5db0ffeeeba8ffd33adebfaaa094 to your computer and use it in GitHub Desktop.
Save ivmos/860b5db0ffeeeba8ffd33adebfaaa094 to your computer and use it in GitHub Desktop.
#!/usr/bin/env kscript
//DEPS com.google.photos.library:google-photos-library-client:1.5.0
import com.google.api.gax.core.FixedCredentialsProvider
import com.google.api.gax.rpc.ApiException
import com.google.auth.oauth2.AccessToken
import com.google.auth.oauth2.UserCredentials
import com.google.photos.library.v1.PhotosLibraryClient
import com.google.photos.library.v1.PhotosLibrarySettings
import com.google.photos.library.v1.proto.BatchCreateMediaItemsResponse
import com.google.photos.library.v1.upload.UploadMediaItemRequest
import com.google.photos.library.v1.upload.UploadMediaItemResponse
import com.google.photos.library.v1.util.NewMediaItemFactory
import com.google.photos.types.proto.Album
import com.google.photos.types.proto.MediaItem
import com.google.rpc.Code
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile
import java.util.*
import java.util.concurrent.TimeUnit
class AnsiColors { companion object { const val ANSI_RESET = "\u001B[0m"; const val ANSI_RED = "\u001B[31m"; const val ANSI_GREEN = "\u001B[32m"; const val ANSI_YELLOW = "\u001B[33m"; const val ANSI_BLUE = "\u001B[34m"; const val ANSI_PURPLE = "\u001B[35m"; const val ANSI_CYAN = "\u001B[36m"; const val ANSI_WHITE = "\u001B[37m"; } }
fun logInfo(message: String) = println("${AnsiColors.ANSI_BLUE}$message${AnsiColors.ANSI_RESET}")
fun logWarn(message: String) = println("${AnsiColors.ANSI_YELLOW}$message${AnsiColors.ANSI_RESET}")
fun logError(message: String) = println("${AnsiColors.ANSI_RED}$message${AnsiColors.ANSI_RESET}")
val defaultWorkingDir = System.getProperty("user.dir");
fun String.runCommand(waitTimeInSeconds : Long = 300, workingDir : String = defaultWorkingDir): Pair<String?, Int> {
try {
val workingDirFile = File(workingDir)
val parts = this.split("\\s".toRegex())
System.out.println(parts)
val proc = ProcessBuilder(*parts.toTypedArray())
.directory(workingDirFile)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
proc.waitFor(waitTimeInSeconds, TimeUnit.SECONDS)
return Pair<String?, Int>(
proc.inputStream.bufferedReader().readText(),
proc.exitValue())
} catch(e: IOException) {
e.printStackTrace()
return Pair<String?, Int>(null, -1)
}
}
val usage = """
Use this tool to... <TODO>
<TODO> required params info
"""
if (args.size < 2) {
//logWarn(usage)
//exitProcess(-1)
}
val dirs = "ls /Users/imosquera/tmp/Takeout/Google_Fotos".runCommand().first?.split("\n")?.filter { s -> !s.contains("-") }?.filter { s -> !s.contains("json") }
println(dirs?.drop(1))
val cred = UserCredentials.newBuilder()
.setClientId("FOO.apps.googleusercontent.com")
.setClientSecret("bar")
.setRefreshToken("bar2")
.build()
var settings = PhotosLibrarySettings.newBuilder()
.setCredentialsProvider(
FixedCredentialsProvider.create(cred)
).build()
val client = PhotosLibraryClient.initialize(settings)
val response = client.listSharedAlbums()
var i = 0;
response.iterateAll().forEach {
t: Album? ->
run {
if (!t?.title?.isEmpty()!!) {
val album = client.createAlbum(t?.title + "exp2")
val folder = "/Users/imosquera/tmp/Takeout/Google_Fotos/" + (t?.title.replace(" ", "_"))
val result = ("ls " + folder).runCommand().first?.split("\n")?.filter{ s -> !s.contains("json")}?.map { s -> s.replace(" ", "-")}
result?.forEach {
if (!it.isEmpty()) {
val path = folder + "/" + it
//println("Will add " + path + " to album " + folder)
val mimeType = when {
it.contains("jpeg", true) -> "image/jpeg"
it.contains("jpg", true) -> "image/jpeg"
it.contains("heic", true) -> "image/heic"
it.contains("mov", true) -> "video/quicktime"
it.contains("mp4", true) -> "video/mp4"
it.contains("gif", true) -> "image/gif"
else -> "ouch"
}
if (mimeType == "ouch") {
println(it + "not known!")
}
val fileUpload = upload(client, path, it, mimeType, it)
println("Upload achieved" + fileUpload?.id)
if (fileUpload != null) {
val res = client.batchAddMediaItemsToAlbum(album.id, listOf(fileUpload.id))
println(res)
} else {
println("Unable to upload")
}
}
}
i++;
if (i > 10) {
System.exit(0)
}
}
}
}
fun upload(photosLibraryClient: PhotosLibraryClient, pathToFile: String, fileName: String, mimeType: String, itemDescription: String): MediaItem? {
// Open the file and automatically close it after upload
try {
RandomAccessFile(pathToFile, "r").use { file ->
// Create a new upload request
val uploadRequest = UploadMediaItemRequest.newBuilder() // The media type (e.g. "image/png")
.setMimeType(mimeType) // The file to upload
.setDataFile(file)
.build()
// Upload and capture the response
val uploadResponse: UploadMediaItemResponse = photosLibraryClient.uploadMediaItem(uploadRequest)
if (uploadResponse.error.isPresent) { // If the upload results in an error, handle it
//val error: Error = uploadResponse.error.get()
println("Error!")
} else { // If the upload is successful, get the uploadToken
val uploadToken = uploadResponse.uploadToken.get()
// Use this upload token to create a media item
try { // Create a NewMediaItem with the following components:
// - uploadToken obtained from the previous upload request
// - filename that will be shown to the user in Google Photos
// - description that will be shown to the user in Google Photos
val newMediaItem = NewMediaItemFactory
.createNewMediaItem(uploadToken, fileName, itemDescription)
val newItems = Arrays.asList(newMediaItem)
val response: BatchCreateMediaItemsResponse = photosLibraryClient.batchCreateMediaItems(newItems)
for (itemsResponse in response.newMediaItemResultsList) {
val status = itemsResponse.status
if (status.code == Code.OK_VALUE) { // The item is successfully created in the user's library
val createdItem = itemsResponse.mediaItem
return createdItem
} else { // The item could not be created. Check the status and try again
println("Item could not be created")
}
}
} catch (e: ApiException) { // Handle error
return null
}
}
}
} catch (e: ApiException) { // Handle error
return null
} catch (e: IOException) { // Error accessing the local file
return null
}
return null
}
@smdjeff
Copy link

smdjeff commented Dec 29, 2022

The out-of-band (OOB) flow has been blocked in order to keep users secure. Follow the Out-of-Band (OOB) flow migration guide linked in the developer docs below to migrate your app to an alternative method.
Related developer documentation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment