Monday, December 29, 2014

Extract Locally Stored MP3s From Google Music On Android

I needed a way to copy the music that is stored locally on my Android device. I couldn't just copy the files over since they have nondescript names like 153.mp3, 214.mp3, etc. Well, I could have just copied them, but I didn't want to have to listen to all of them and then rename them appropriately. I could have downloaded them with Google Music Manager but there is a limit to the number of times you can download them (two, I think). But more importantly to me, I have a data cap and I have already downloaded them by selecting "Keep on device" and didn't want to download them again. What I did was use the music database to gather information and use that to copy and rename the files.

What is needed:
-a rooted device in order to access the music files
-adb installed on the PC (see here for info on adb)
-Python installed on the PC (although I imagine this could be done in a Bash script too)
-the Android device connected to the PC so adb can access it

A few notes:
-The music is copied to the current folder as Artist/Album/Tracks.
-Copying the MP3 files from the device can take a while. Patience is a virtue.
-The paths created are *nix style, i.e. uses forward slashes. Windows users might need to change to back slashes
-There isn't any error checking, so if adb fails or sqlite3 isn't installed, who knows what will happen?

Finally, here is the python script.

#!/usr/bin/python
# Extract locally stored music from Google Music and rename the files properly
# 12-08-2014  Created by Bill Blankenship

import subprocess
import sqlite3
import os

# Get a copy of the music database
subprocess.check_call(["adb", "pull", "/data/data/com.google.android.music/databases/music.db"])
subprocess.check_call(["adb", "pull", "/data/data/com.google.android.music/databases/music.db-journal"])

# Get the mp3 files
raw_input("Getting ready to copy mp3 files from the device. This can take a while.\nPress Ctl-C to abort or Enter to continue...")
subprocess.check_call(["adb", "pull", "/data/data/com.google.android.music/files/music/"])

# Get needed info from the database
db = sqlite3.connect("music.db")
cursor = db.cursor()
cursor.execute('''select Id, LocalCopyPath, Title, Album, Artist, TrackNumber from MUSIC where LocalCopyPath is not NULL''')
all_rows = cursor.fetchall()

# Iterate through each song 
for row in all_rows:
  # Replace "/" with "-" in track names 'cause they're trouble
  # Apparently tuples are immutable, so create more variables 
  LocalPath = row[1].replace("/","-")
  Title = row[2].replace("/","-")
  Album = row[3].replace("/","-")
  Artist = row[4].replace("/","-")
  TrackNumber = row[5]

  # Make TrackNumber two digits (and a string)
  if TrackNumber < 10:
    TrackNumber = "0"+str(TrackNumber)
  else:
    TrackNumber = str(TrackNumber)

  # Create Artist folder if needed
  if not os.path.isdir(Artist):
    os.makedirs(Artist)
  # Create Album folder if needed
  if not os.path.isdir(Artist+"/"+Album):
    os.makedirs(Artist+"/"+Album)
  # Move and rename tracks
  print("Copying "+LocalPath+" to "+Artist+"/"+Album+"/"+TrackNumber+"-"+Title+".mp3")
  os.rename(LocalPath, Artist+"/"+Album+"/"+TrackNumber+"-"+Title+".mp3")
db.close()

# Remove the music database files
os.remove("music.db")
os.remove("music.db-journal")