-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Matthew Sanders
committed
Mar 12, 2022
1 parent
6249cf2
commit 3fe283b
Showing
20 changed files
with
4,897 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# EditorConfig is awesome: https://EditorConfig.org | ||
|
||
# top-most EditorConfig file | ||
root = true | ||
|
||
[*] | ||
indent_style = space | ||
indent_size = 4 | ||
end_of_line = lf | ||
charset = utf-8 | ||
trim_trailing_whitespace = true | ||
insert_final_newline = false | ||
|
||
[*.{css,cjs,js,jsx,ts,tsx,mjs,json,jsonc}] | ||
indent_size = 2 | ||
insert_final_newline = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
module.exports = { | ||
env: { | ||
es2021: true, | ||
node: true, | ||
}, | ||
extends: [ | ||
'airbnb-base', | ||
], | ||
parserOptions: { | ||
ecmaVersion: 12, | ||
sourceType: 'module', | ||
}, | ||
rules: { | ||
'max-len': 'off', | ||
'no-console': 'off', | ||
'import/extensions': 'off', | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "stylelint-config-standard" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,72 @@ | ||
# google-voice-transformer | ||
# google-voice-transformer | ||
|
||
This application transforms your Google Voice text messages to much more readable formats. | ||
|
||
## Features | ||
|
||
* Combines all messages, per user, into single files so you can _finally_ search through your messages. | ||
* Correctly displays media files instead of simply presenting the user with a link to the media file. | ||
* Converts all video files to mp4. | ||
* Groups all messages by the full date. | ||
* Fixes the references to media files in your messages. Google does not include the file extension in the anchor tag. Google also references broken filenames due to having truncated long filenames in the html source. | ||
|
||
## Screenshots | ||
|
||
<a href="https://user-images.githubusercontent.com/4317724/158003471-2fba9d7f-cd5c-4c8c-97d3-96919a28b609.png" title="default template"> | ||
<img src="https://user-images.githubusercontent.com/4317724/158003471-2fba9d7f-cd5c-4c8c-97d3-96919a28b609.png" width="350"> | ||
</a> <a href="https://user-images.githubusercontent.com/4317724/158003476-3d5376f6-8ebb-4ee5-8003-c7c33ba2c041.png" title="WhatsApp template"> | ||
<img src="https://user-images.githubusercontent.com/4317724/158003476-3d5376f6-8ebb-4ee5-8003-c7c33ba2c041.png" width="350"> | ||
</a> | ||
|
||
## Requirements | ||
|
||
Your [Google Takeout][1] data. Make sure that it includes your Google Voice data. Extract the archive and save the `Takeout` directory to disk. | ||
|
||
![Test Image 3](docs/images/takeout.png) | ||
|
||
## Usage | ||
|
||
```posh | ||
Usage: google-voice-transformer [options] | ||
Convert the Google Takeout format to a more readable format. | ||
Options: | ||
-V, --version output the version number | ||
--path <string> (optional) The path to your Takeout folder. The program will look for this folder in the same | ||
directory | ||
--template <type> (optional) The output file template (choices: "default", "whatsapp") | ||
-h, --help display help for command | ||
``` | ||
|
||
This application is ran from the command line. The options are optional. An `output` folder will be created in the directory this application is ran. | ||
|
||
`--path`: if you leave this empty the application will default to looking for your `Takeout` folder in the same directory from which this application was ran. | ||
|
||
`--template`: if you leave this empty the application will use a style similar to Google Voice. | ||
|
||
### Command line examples | ||
|
||
_Default template with the Takeout folder in the same directory._ | ||
|
||
```posh | ||
.\gvt-win.exe | ||
``` | ||
|
||
_WhatsApp template with the Takeout folder in the same directory._ | ||
|
||
```posh | ||
.\gvt-win.exe --template whatsapp | ||
``` | ||
|
||
_WhatsApp template with the Takeout folder on another drive._ | ||
|
||
```posh | ||
.\gvt-win.exe --template whatsapp --path D:\Takeout | ||
``` | ||
|
||
## Issues | ||
|
||
* If you have messages that are more than a decade old it is normal to see a warning on the command line about missing media files. This is due to Google not including your very old media files into the Takeout archive. | ||
|
||
[1]: https://takeout.google.com/settings/takeout "Google Takeout" |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
{ | ||
"name": "google-voice-transformer", | ||
"version": "0.1.0", | ||
"description": "Convert Google Voice files from Google Takeout to be more readable", | ||
"main": "app.js", | ||
"license": "GPL-3.0-or-later", | ||
"homepage": "https://github.com/Clearmist/google-voice-transformer", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/Clearmist/google-voice-transformer" | ||
}, | ||
"type": "module", | ||
"scripts": { | ||
"build": "webpack && pkg build/app.js -o build/Release/gvt -t linux,macos,win", | ||
"run": "node src/app.js", | ||
"run-whatsapp": "node src/app.js --template whatsapp" | ||
}, | ||
"pkg": { | ||
"outputPath": "build" | ||
}, | ||
"dependencies": { | ||
"commander": "^9.0.0", | ||
"cross-env": "^7.0.3", | ||
"fast-glob": "^3.2.11", | ||
"handbrake-js": "^6.0.0", | ||
"node-html-parser": "^5.2.0", | ||
"webpack": "^5.70.0" | ||
}, | ||
"devDependencies": { | ||
"css-loader": "^6.7.1", | ||
"eslint": "^8.10.0", | ||
"eslint-config-airbnb-base": "^15.0.0", | ||
"eslint-plugin-import": "^2.25.4", | ||
"html-loader": "^3.1.0", | ||
"pkg": "^5.5.2", | ||
"stylelint": "^14.5.3", | ||
"stylelint-config-standard": "^25.0.0", | ||
"to-string-loader": "^1.2.0", | ||
"webpack-cli": "^4.9.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import fg from 'fast-glob'; | ||
import { globOptions, options } from './library/options.js'; | ||
import { createFolder } from './library/output.js'; | ||
import parseSourceFiles from './library/parse.js'; | ||
|
||
// Get a list of text messages. | ||
const textSources = fg.sync([`${options.callsPath}/* - Text - *.{htm,html}`], globOptions); | ||
|
||
if (Array.isArray(textSources)) { | ||
createFolder('output/media'); | ||
|
||
parseSourceFiles(textSources); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
const components = { | ||
whatsapp: { | ||
metadata: `<span class="metadata"> | ||
<span class="time">{{time}}</span> | ||
</span>`, | ||
}, | ||
}; | ||
|
||
export default components; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import fs from 'fs'; | ||
import path from 'path'; | ||
import hbjs from 'handbrake-js'; | ||
|
||
export default function convert(details) { | ||
return new Promise((resolve, reject) => { | ||
const extension = path.extname(details.fullPath); | ||
const relativePath = `${details.relativePath.replace(extension, '')}.mp4`; | ||
const output = path.join(process.cwd(), 'output', relativePath); | ||
|
||
const options = { | ||
input: details.fullPath, | ||
output, | ||
}; | ||
|
||
if (fs.existsSync(output)) { | ||
console.log(`Skipping existing video file: ${details.filename}`); | ||
|
||
resolve(); | ||
} else { | ||
console.log(`Converting video file: ${details.filename}`); | ||
|
||
hbjs.exec(options, (err, stdout, stderr) => { | ||
if (err) { | ||
console.error(stderr); | ||
} | ||
|
||
resolve(); | ||
}); | ||
} | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import path from 'path'; | ||
import { options } from './options.js'; | ||
import components from './components.js'; | ||
|
||
const months = { | ||
abbreviated: { | ||
0: 'Jan', | ||
1: 'Feb', | ||
2: 'Mar', | ||
3: 'Apr', | ||
4: 'May', | ||
5: 'Jun', | ||
6: 'Jul', | ||
7: 'Aug', | ||
8: 'Sep', | ||
9: 'Oct', | ||
10: 'Nov', | ||
11: 'Dec', | ||
}, | ||
full: { | ||
0: 'January', | ||
1: 'February', | ||
2: 'March', | ||
3: 'April', | ||
4: 'May', | ||
5: 'June', | ||
6: 'July', | ||
7: 'August', | ||
8: 'September', | ||
9: 'October', | ||
10: 'November', | ||
11: 'December', | ||
}, | ||
}; | ||
|
||
export function formatTime(timestamp) { | ||
const date = new Date(timestamp); | ||
const hours24 = date.getHours(); | ||
const hours12 = hours24 > 12 ? hours24 - 12 : hours24; | ||
const meridian = hours24 > 11 ? 'PM' : 'AM'; | ||
|
||
let timeString = date.toLocaleTimeString(); | ||
|
||
if (options.template === 'whatsapp') { | ||
timeString = `${hours12}:${date.getMinutes()} ${meridian}`; | ||
} else { | ||
const left = `${months.abbreviated[date.getMonth()]} ${date.getDate()}`; | ||
const right = `${hours12}:${date.getMinutes()} ${meridian}`; | ||
|
||
timeString = `<span title="${left} ${date.getFullYear()}, ${right}"> | ||
${left}, ${right} | ||
</span>`; | ||
} | ||
|
||
return timeString; | ||
} | ||
|
||
export function formatSeparator(previousTimestamp, timestamp) { | ||
let separator = false; | ||
|
||
const previousDate = new Date(previousTimestamp); | ||
const date = new Date(timestamp); | ||
|
||
if (previousTimestamp === '' || previousDate.toDateString() !== date.toDateString()) { | ||
separator = `${months.full[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`; | ||
} | ||
|
||
return separator; | ||
} | ||
|
||
function formatMetadata(message) { | ||
let metadata = ''; | ||
|
||
if (message.type !== 'separator') { | ||
if (options.template === 'whatsapp') { | ||
metadata = components.whatsapp.metadata.replace('{{time}}', message.metadata.time); | ||
} else { | ||
metadata = `</div><div class="status ${message.direction}">${message.metadata.time}`; | ||
} | ||
} | ||
|
||
return metadata; | ||
} | ||
|
||
export function formatMessage(message) { | ||
let metadata = formatMetadata(message); | ||
let html = message.messageText; | ||
let divClass = 'message'; | ||
|
||
if (message.type === 'audio') { | ||
html = `<div class="media audio"> | ||
<audio controls src="${message.messageText.relativePath}"> | ||
</div>`; | ||
} else if (message.type === 'image') { | ||
html = `<div class="media image"> | ||
<a target="_blank" href="${message.messageText.relativePath}"> | ||
<img src="${message.messageText.relativePath}" /> | ||
</a> | ||
</div>`; | ||
metadata = ''; | ||
} else if (message.type === 'vcard') { | ||
html = `<a href="${message.messageText.relativePath}">Contact card</a>`; | ||
} else if (message.type === 'video') { | ||
// All video is either already mp4 or has been converted to mp4. | ||
const relativePath = message.messageText.relativePath.replace(path.extname(message.messageText.relativePath), '.mp4'); | ||
|
||
html = `<div class="media video"> | ||
<video controls> | ||
<source src="${relativePath}" type="video/mp4"> | ||
Sorry, your browser doesn't support embedded videos. | ||
</video> | ||
</div>`; | ||
} else if (message.type === 'separator') { | ||
divClass = ''; | ||
} | ||
|
||
return `\n<div class="${divClass} ${message.direction}"> | ||
${html}${metadata} | ||
</div>`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import fs from 'fs'; | ||
import path from 'path'; | ||
import { Command, Option } from 'commander'; | ||
|
||
export const program = new Command(); | ||
|
||
program | ||
.name('google-voice-transformer') | ||
.description('CLI to convert Google Takeout (Voice) to a more readable format') | ||
.version('0.1.0'); | ||
|
||
program | ||
.description('Convert the Google Takeout format to a more readable format.') | ||
.option('--path <string>', '(optional) The path to your Takeout folder. The program will look for this folder in the same directory') | ||
.addOption(new Option('--template <type>', '(optional) The output file template').choices(['default', 'whatsapp'])); | ||
|
||
export const options = {}; | ||
|
||
program.parse(); | ||
|
||
const userInput = program.opts(); | ||
|
||
// Clean the template option. | ||
userInput.template = userInput.template ?? 'default'; | ||
options.template = userInput.template.toLowerCase(); | ||
|
||
// Verify that the path to the data directory exists. | ||
options.path = path.normalize(userInput.path ?? path.join(process.cwd(), 'Takeout')).replaceAll('\\', '/'); | ||
|
||
if (fs.existsSync(options.path) === false) { | ||
program.error('The path to the Takeout folder does not exist. Either run this program from the same place where you are storing your Takeout directory or use the --path <fullpath> option to point the program to the location of your Takeout directory.'); | ||
} | ||
|
||
options.callsPath = path.join(options.path, 'Voice', 'Calls').replaceAll('\\', '/'); | ||
|
||
// Verify that the Calls sub-directory exists in the Takeout folder. | ||
if (fs.existsSync(options.callsPath) === false) { | ||
program.error('Your Takeout folder does not contain the ./Takeout/Voice/Calls subdirectory.'); | ||
} | ||
|
||
export const globOptions = { | ||
dot: false, | ||
onlyFiles: true, | ||
}; |
Oops, something went wrong.