Skip to content

Instantly share code, notes, and snippets.

@Fluf22
Last active July 27, 2023 04:10
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Fluf22/d5cce49db7bbc0e049b1841639055304 to your computer and use it in GitHub Desktop.
Save Fluf22/d5cce49db7bbc0e049b1841639055304 to your computer and use it in GitHub Desktop.
Transfer pictures and videos from unzipped Google Takeout folder to Nextcloud folder
/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// IMPROVEMENTS TO DO:
// - Everything is synchronous to avoid file copy conflict
// - Sadly, readline take over the standard input and block CTRL + C and CTRL + D signals
const fs = require('fs');
const readline = require('readline');
const currDir = process.cwd(); // Get current directory path
const nextDir = process.argv[2]; // Get nextcloud directory path from arguments
const removeEnabled = process.argv.length > 3 && process.argv[3].includes("--remove=") && process.argv[3].split("=")[1] === "true"; // Get --remove option from arguments
// If you are not french, you should change this file name
// It is a file stored in every photos folder in order to give some informations about it
// I use it to know the real photos folder name, which isn't the one that Google use to store your photos
const metadataFilename = "métadonnées.json";
// File extensions that will be used as a reference to know which file to transfer
// This list is case INSENSITIVE. We will test case in the script
const fileExtensionList = [
"jpg",
"jpeg",
"png",
"gif",
"mp4"
];
// Initiate readline interface
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// Some logs
console.log(`Old files removal is ${removeEnabled ? "" : "not"} enabled`)
console.log(`Directories will be copied to "${nextDir}"!`);
console.log(`Opening current dir: ${currDir}...`)
// Get current directory file list
const files = fs.readdirSync(currDir);
// Do a loop on this directory list
for (let idx1 = 0; idx1 < files.length; ++idx1) {
let file = files[idx1];
const stats = fs.lstatSync(`${currDir}/${file}`); // Get informations about this directory
// Test if it is really a directory
if (stats.isDirectory()) {
let realDirName = "";
// Google Photos stores photos from general view in dated directories
let isDatedFolder = file.slice(0, 3) === "199" || file.slice(0, 2) === "20";
// Send photos from Google Photos general view to Nextcloud general Photos directory
// Otherwise, send photos to Nextcloud Pictures folder, in a subdirectory
if (isDatedFolder) {
realDirName = "Photos";
} else if (fs.existsSync(`${currDir}/${file}/${metadataFilename}`)) { // Test if metadata filename exists in directory
const metadataStr = fs.readFileSync(`${currDir}/${file}/${metadataFilename}`, "utf-8"); // Read the file as a string
const metadata = JSON.parse(metadataStr); // Parse it to get JSON
if (metadata && metadata.title) { // If Google used a cleaner name than the one you used to name your folder...
realDirName = "Pictures/" + metadata.title; // ...then replace it to keep YOUR choice...
} else {
realDirName = "Pictures/" + file; // ...else, use the currently processed folder name
}
} else {
realDirName = file; // we never should go here...
}
// Some log
console.log(`${file}: Begin images copy to ${isDatedFolder ? "photos folder" : realDirName + " specific folder"}...`);
// Get a list of all the photos inside this folder
const dirFiles = fs.readdirSync(`${currDir}/${file}`);
let createdDir = false; // Shortcut to know if this folder has been created in Nextcloud
let imgCount = 0; // Keep an eye on copy progress
// Loop over all the photos
for (let idx2 = 0; idx2 < dirFiles.length; ++idx2) {
let img = dirFiles[idx2]; // The processed photo/video
// Only copy photos and videos
if (fileExtensionList.includes(img.split(".")[img.split(".").length - 1].toLowerCase())) {
// Create subdirectory if it doesn't exist
if (!createdDir && !fs.existsSync(`${nextDir}/${realDirName}`)) {
fs.mkdirSync(`${nextDir}/${realDirName}`);
createdDir = true;
}
// Test if the processed file already exists in destination folder
// Useful if the script fails and you have to execute it again
if (!fs.existsSync(`${nextDir}/${realDirName}/${img}`)) {
// Comfort logs - overwrite current line on console
if (idx2 !== 0) {
readline.cursorTo(process.stdout, 0); // move cursor to beginning of line
readline.moveCursor(process.stdout, 0, -1); // move cursor to previous line
readline.clearLine(process.stdout, 1); // clear line
}
rl.write(`${img}: copying to ${realDirName}...\n`); // write text
// Actually do the copy
fs.copyFileSync(`${currDir}/${file}/${img}`, `${nextDir}/${realDirName}/${img}`);
imgCount = imgCount + 1; // Copied one more file to destination, away from Google ;)
}
// Delete file if remove option is enabled and if it exists (useful in case of script failure/re-execution)
if (removeEnabled && fs.existsSync(`${currDir}/${file}/${img}`)) {
fs.unlinkSync(`${currDir}/${file}/${img}`);
}
}
// Comfort logs
if (imgCount > 0 && idx2 === dirFiles.length - 1) {
readline.cursorTo(process.stdout, 0); // move cursor to beginning of line
readline.moveCursor(process.stdout, 0, -1); // move cursor to previous line
readline.clearLine(process.stdout, 1); // clear line
rl.write(`Copied ${imgCount} images!\n`); // write text
}
};
realDirName = ""; // Reset destination directory name
}
};
rl.close(); // Close readline interface
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment