Compare commits
	
		
			5 Commits
		
	
	
		
			dce5236b1f
			...
			efdb203e2c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| efdb203e2c | |||
| 1aaa28d98d | |||
| aa7c0fb106 | |||
| a35f9d05d4 | |||
| 9f34fe4a8a | 
							
								
								
									
										87
									
								
								audible-converter.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								audible-converter.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | |||||||
|  | import configparser | ||||||
|  | from requests import get | ||||||
|  | import os | ||||||
|  | import json | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  | class AudibleBook(object): | ||||||
|  |  | ||||||
|  |     def __init__(self, filePath, activationBytes): | ||||||
|  |         self.filePath = filePath | ||||||
|  |         self.activationBytes = activationBytes | ||||||
|  |         self.metadata = json.loads(os.popen('ffprobe -i {} -show_format \ | ||||||
|  |             -print_format json'.format(filePath)).read())['format']['tags'] | ||||||
|  |         self.chapters = json.loads(os.popen('ffprobe -i {} -print_format json \ | ||||||
|  |             -show_chapters -loglevel error -sexagesimal'.format(self.filePath)) | ||||||
|  |                           .read())['chapters'] | ||||||
|  |  | ||||||
|  |     # getPath() returns path to origonal audible file. | ||||||
|  |     def getPath(self): | ||||||
|  |         return self.filePath | ||||||
|  |  | ||||||
|  |     # getMetadata() returns audio metadata for a given filepath. | ||||||
|  |     def getMetadata(self): | ||||||
|  |         return self.metadata | ||||||
|  |  | ||||||
|  |     # getChapters() returns chapter metadata for a given filepath. | ||||||
|  |     def getChapters(self): | ||||||
|  |         return self.chapters | ||||||
|  |  | ||||||
|  |     # getTitle() returns audiobook title. | ||||||
|  |     def getTitle(self): | ||||||
|  |         return self.getMetadata()['title'] | ||||||
|  |  | ||||||
|  |     def removeDRM(self, fileName): | ||||||
|  |         os.system('ffmpeg -activation_bytes {} -i {} -c copy "{}.m4b"' | ||||||
|  |                   .format(self.activationBytes, self.filePath, fileName)) | ||||||
|  |         print('test') | ||||||
|  |  | ||||||
|  | # getHash() returns the hash of a given file. | ||||||
|  | def getHash(filePath): | ||||||
|  |     with open(filePath, 'rb') as f: | ||||||
|  |         f.seek(653) | ||||||
|  |         data = f.read(20) | ||||||
|  |         f.close() | ||||||
|  |     return data.hex() | ||||||
|  |  | ||||||
|  | # getActivationBytes returns the bytes needed to decrypt a given hash.  | ||||||
|  | def getActivationBytes(filehash): | ||||||
|  |     headers = {'User-Agent': 'audible-converter'} | ||||||
|  |     response = get('https://aax.api.j-kit.me/api/v2/activation/{}'.format(filehash), | ||||||
|  |                    headers=headers) | ||||||
|  |     return json.loads(response.text)['activationBytes'] | ||||||
|  |  | ||||||
|  | # createConfig() creates a config file. | ||||||
|  | def createConfig(configFilePath, filePath): | ||||||
|  |     activationBytes = getActivationBytes(getHash(filePath)) | ||||||
|  |     f = open(configFilePath, 'w') | ||||||
|  |     f.write('activationBytes={}'.format(activationBytes)) | ||||||
|  |     f.close() | ||||||
|  |  | ||||||
|  | configFilePath='./settings.conf' | ||||||
|  | audibleBookPath=sys.argv[1] | ||||||
|  |  | ||||||
|  | # Check if config file exists and creates one if needed. | ||||||
|  | if not os.path.exists(configFilePath): | ||||||
|  |     print('Creating config file') | ||||||
|  |     createConfig(configFilePath, audibleBookPath) | ||||||
|  |      | ||||||
|  | configString = '[Settings]\n' + open(configFilePath).read() | ||||||
|  | configParser = configparser.RawConfigParser() | ||||||
|  | configParser.read_string(configString) | ||||||
|  | activationBytes=configParser.get('Settings', 'activationBytes') | ||||||
|  |      | ||||||
|  | book = AudibleBook(audibleBookPath, activationBytes) | ||||||
|  |  | ||||||
|  | for c in book.getChapters(): | ||||||
|  |     start=c['start'] | ||||||
|  |     end=c['end'] | ||||||
|  |     title=c['tags']['title'] | ||||||
|  |     print(start, end, title) | ||||||
|  |     #os.system('ffmpeg -i {} -acodec copy -vcodec copy -ss {} -t {} OUTFILE-{}.m4a'.format(filePath, start, end, title)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | print(book.getTitle()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -1,5 +1,9 @@ | |||||||
| #!/usr/bin/env bash | #!/usr/bin/env bash | ||||||
|  |  | ||||||
|  | cleanString() { | ||||||
|  |      tr -d '"' | ||||||
|  | } | ||||||
|  |  | ||||||
| # getHash returns the hash of a given file. | # getHash returns the hash of a given file. | ||||||
| function getHash() { | function getHash() { | ||||||
|     echo $(xxd -p -l20 -s 653 ${1}) |     echo $(xxd -p -l20 -s 653 ${1}) | ||||||
| @@ -8,7 +12,7 @@ function getHash() { | |||||||
| # getActivationBytes returns the bytes needed to decrypt a given hash.  | # getActivationBytes returns the bytes needed to decrypt a given hash.  | ||||||
| function getActivationBytes() { | function getActivationBytes() { | ||||||
|     json=$(curl -A 'audible-converter' https://aax.api.j-kit.me/api/v2/activation/${1}) |     json=$(curl -A 'audible-converter' https://aax.api.j-kit.me/api/v2/activation/${1}) | ||||||
|     echo $(echo $json | jq '.activationBytes' | tr -d '"') |     echo $(echo $json | jq '.activationBytes' | cleanString) | ||||||
| } | } | ||||||
|  |  | ||||||
| # createConfig creates a config file. | # createConfig creates a config file. | ||||||
| @@ -27,11 +31,11 @@ function checkAudibleFile() { | |||||||
| } | } | ||||||
|  |  | ||||||
| function removeDRM() { | function removeDRM() { | ||||||
|     ffmpeg -activation_bytes ${2} -i ${1} -c copy "$(basename ${1} | cut -f 1 -d '.').m4b" |     ffmpeg -activation_bytes ${2} -i ${1} -c copy "${3}.m4b" | ||||||
| } | } | ||||||
|  |  | ||||||
| function getChapterJSON() { | function getChapterJSON() { | ||||||
|     echo $(ffprobe -i ${1} -print_format json -show_chapters -loglevel error -sexagesimal) |     echo $(ffprobe -i ${1} -print_format json -show_chapters -loglevel error -sexagesimal | jq '.chapters') | ||||||
| } | } | ||||||
|  |  | ||||||
| function getMetadataJSON() { | function getMetadataJSON() { | ||||||
| @@ -39,7 +43,11 @@ function getMetadataJSON() { | |||||||
| } | } | ||||||
|  |  | ||||||
| function getTitle() { | function getTitle() { | ||||||
|     echo $(echo ${1} | jq '.title') |     echo $(echo ${1} | jq '.title' | cleanString) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function fileSafe() { | ||||||
|  |      sed -e 's/[^A-Za-z0-9._-]/-/g' | tr '[:upper:]' '[:lower:]' | sed -e 's/--/-/g' | ||||||
| } | } | ||||||
|  |  | ||||||
| settingsFile='./settings.conf' | settingsFile='./settings.conf' | ||||||
| @@ -57,5 +65,18 @@ fi | |||||||
| . $settingsFile | . $settingsFile | ||||||
|  |  | ||||||
| metadataJSON=$(getMetadataJSON $audibleFile) | metadataJSON=$(getMetadataJSON $audibleFile) | ||||||
| json=$(getMetadataJSON $audibleFile) | chapterJSON=$(getChapterJSON $audibleFile) | ||||||
| removeDRM $audibleFile $activationBytes | name=$(getTitle "${metadataJSON}" | fileSafe) | ||||||
|  |  | ||||||
|  | if [ ! -f "${name}.m4b" ]; then | ||||||
|  |     removeDRM $audibleFile $activationBytes $name | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | mkdir -p $name | ||||||
|  |  | ||||||
|  | data=$(echo "${chapterJSON}" | jq '.[] | "\(.start),\(.end),\(.tags.title)"') | ||||||
|  | echo $data | ||||||
|  | IFS=' ' read -r -a array <<< "$data" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | echo $(echo "${chapterJSON}" | jq '.[] | .start,.end,.tags.title') | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user