package jml.Mp3Tags; import android.content.Context; import android.util.Log; import com.google.appinventor.components.annotations.*; import com.google.appinventor.components.runtime.*; import com.google.appinventor.components.common.ComponentCategory; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.MediaMetadataRetriever; import com.google.appinventor.components.common.PropertyTypeConstants; import com.google.appinventor.components.runtime.util.MediaUtil; import java.io.UnsupportedEncodingException; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.FileInputStream; import java.io.ByteArrayOutputStream; import java.io.BufferedOutputStream; import android.widget.Toast; import java.nio.ByteBuffer; @DesignerComponent(version = 8, description = "obtention et edition des tags d'un mp3" + " par jm latour " + "https://community.appinventor.mit.edu/t/mp3tags-extension/11451/48", category = ComponentCategory.EXTENSION, nonVisible = true, iconName = "https://community.appinventor.mit.edu/uploads/default/original/3X/5/5/55406ab8760ed2613fc7c28b0e9193542b6bf438.png") @SimpleObject(external = true) public class Mp3Tags extends AndroidNonvisibleComponent { private ComponentContainer container; public static String fichier=""; public static String test=""; /** * @param container container, component will be placed in */ public Mp3Tags(ComponentContainer container) { super(container.$form()); this.container = container; } MediaMetadataRetriever metaRetriever; //************* /* // permet de savoir où on est passé par modif de la variable test @SimpleFunction(description = "procedure de test pour mise au point extension") public String Test(String filePath, String ImgFilePath) throws IOException { test="test"; EditAlbum_Arts( filePath, ImgFilePath); return test; } */ //************* @SimpleFunction(description = "return file's artist") public String GetArtist(String filePath){ filePath=defineDir(filePath); if (new File(filePath).exists()) { return GetTextTag(filePath, "TPE1"); }else return""; } @SimpleFunction(description = "Edit Artist's Tag") public void EditArtist(String filePath,String NewTag) throws IOException { filePath=defineDir(filePath); if (new File(filePath).exists()) { replaceTag("TPE1",filePath,NewTag); } } //**************** @SimpleFunction(description = "return file's album") public String GetAlbum(String filePath){ filePath=defineDir(filePath); if (new File(filePath).exists()) { return GetTextTag(filePath, "TALB"); }else return""; } @SimpleFunction(description = "Edit Album's Tag") public void EditAlbum(String filePath,String NewTag) throws IOException { filePath=defineDir(filePath); if (new File(filePath).exists()) { replaceTag("TALB",filePath,NewTag); } } //*********** @SimpleFunction(description = "return file's title") public String GetTitle(String filePath){ filePath=defineDir(filePath); if (new File(filePath).exists()) { return GetTextTag(filePath, "TIT2"); }else return""; } @SimpleFunction(description = "Edit Title's Tag") public void EditTitle(String filePath,String NewTitle) throws IOException { filePath=defineDir(filePath); if (new File(filePath).exists()) { replaceTag("TIT2",filePath,NewTitle); } } //********* @SimpleFunction(description = "return file's genre") public String GetGenre(String filePath){ filePath=defineDir(filePath); if (new File(filePath).exists()) { return GetTextTag(filePath, "TCON"); }else return""; } @SimpleFunction(description = "Edit Genre's Tag") public void EditGenre(String filePath,String NewTag) throws IOException { filePath=defineDir(filePath); if (new File(filePath).exists()) { replaceTag("TCON",filePath,NewTag); } } //********** @SimpleFunction(description = "Edit Comment's Tag") public void EditComment(String filePath,String NewTag) throws IOException { filePath=defineDir(filePath); if (new File(filePath).exists()) { NewTag="fra "+NewTag; replaceTag("COMM", filePath, NewTag); } } @SimpleFunction(description = "Get Comment's Tag") public String GetComment(String filePath){ filePath=defineDir(filePath); if (new File(filePath).exists()) { String result=GetTextTag(filePath, "COMM"); if (result==""){ return ""; }else{ return result.substring(4); //extrait l'info de langue } }else return ""; } //*********** @SimpleFunction(description = "Edit Group's Tag") public void EditGroup(String filePath,String NewTag) throws IOException { filePath=defineDir(filePath); if (new File(filePath).exists()) { replaceTag("TIT1", filePath, NewTag); } } @SimpleFunction(description = "Get Group's Tag") public String GetGroup(String filePath){ filePath=defineDir(filePath); if (new File(filePath).exists()) { return GetTextTag(filePath, "TIT1"); }else return""; } //************ @SimpleFunction(description = "Get volume adjustment's Tag (return -127 if not found)" ) public byte GetVolumeAdjustment(String filePath){ filePath=defineDir(filePath); if (new File(filePath).exists()) { return GetByteTag(filePath, "RVAD"); }else return 0; } @SimpleFunction(description = "Edit volume adjustment's Tag (must be an integer from 0 to 255)") public void EditVolumeAdjustment(String filePath,byte NewVolAdj) throws IOException { filePath=defineDir(filePath); if (new File(filePath).exists()) { replaceByteTag("RVAD",filePath,NewVolAdj); } } //*********** @SimpleFunction(description = "Edit Rate's Tag") public void EditRate(String filePath,int NewRate) throws IOException { filePath=defineDir(filePath); if (new File(filePath).exists()) { replacePopmTag("POPM",filePath,NewRate); } } @SimpleFunction(description = "Get Rate Tag (return -1 if not found)") public int GetRate(String filePath) throws IOException { filePath=defineDir(filePath); if (new File(filePath).exists()) { return GetPopmTag(filePath, "POPM"); } else return -1; } @SimpleFunction(description = "Get Rate Comment Tag ") public String GetRateComment(String filePath) throws IOException { filePath=defineDir(filePath); if (new File(filePath).exists()) { return GetRateCommentTag(filePath, "POPM"); } else return ""; } //*********** @SimpleFunction(description = "Edit Last play date's Tag") public void EditLastPlayDate(String filePath,long NewLpd) throws IOException { filePath=defineDir(filePath); if (new File(filePath).exists()) { replaceLongTag("LPDA",filePath,NewLpd); } } @SimpleFunction(description = "Get Last play date Tag (return -1 if not found)") public long GetLastPlayDate(String filePath) throws IOException { filePath=defineDir(filePath); if (new File(filePath).exists()) { return GetLongTag(filePath, "LPDA"); } else return -1; } //************* @SimpleFunction(description = "return file's path") public String GetPath(String filePath){ String path=defineDir(filePath); //path=form.getExternalFilesDir("Mp3Tags").getAbsolutePath()+"/tmp.png"; return path; } String defineDir(String path){ if(path.startsWith("//")){ return "/storage/emulated/0/AppInventor/assets/"+path.substring(2,path.length()); }else{ if(path.startsWith("/")){ return "/mnt/sdcard"+path; }else { if (path.startsWith("file:///") ) { return path.substring(7,path.length()); } else return path; } } } //********** @SimpleFunction(description = "return file's album art") public String GetAlbum_Art(String filePath){ String song=defineDir (filePath); File file=new File(song); byte[] art = new byte[0]; Bitmap songImage=null; metaRetriever = new MediaMetadataRetriever(); if (file.exists()) { metaRetriever.setDataSource(song); try {song="try"; art = metaRetriever.getEmbeddedPicture(); songImage = BitmapFactory .decodeByteArray(art, 0, art.length); } catch (Exception e) {song="exception"; } }else { //song="file not found : "+ file.toString(); } String filename =form.getExternalFilesDir("Mp3Tags").getAbsolutePath()+"/tmp.png"; String reponse=saveBitmap(songImage,filename); return reponse; } @SimpleFunction(description = "Edit file's album art " + " Must be jpg or png ") public void EditAlbum_Arts(String filePath, String ImgFilePath) throws IOException { test="EditAlbum_Arts"; filePath=defineDir(filePath); if (new File(filePath).exists()) { test="filePath"; ImgFilePath=defineDir(ImgFilePath); if (new File(ImgFilePath).exists()) { test="ImgFilePath"; byte[] data=getByteArrayFromAudio(ImgFilePath); test=String.valueOf(data.toString().length()); replaceImage("APIC", filePath, data); test="fin Edit"+String.valueOf((int)data.length); } } } //************** @SimpleFunction(description = "Get Lyrics") public String GetLyrics(String filePath) throws IOException { filePath=defineDir(filePath); if (new File(filePath).exists()) { byte[] bytesOfAudio = TagBytes(filePath); int positionTag = chercheTagChaine(bytesOfAudio, "USLT"); if (positionTag == 0) { //Toast.makeText(this, "Tag doesn't exists", Toast.LENGTH_LONG).show(); return ""; } else { int longueurDuTag=(bytesOfAudio[positionTag+7]+256)%256+((bytesOfAudio[positionTag+6]+256)%256)*256; int accent=0; if (bytesOfAudio[positionTag+10]==1){ accent=1;} //si il y a des accents il y a 1 octet en plus avant les paroles byte[] lyricsBytes=new byte[longueurDuTag-5-accent]; for (int f = 0; f < longueurDuTag-5-accent; f++) { //copie des octets jusqu'à l'octet de Rating lyricsBytes[f] = bytesOfAudio[positionTag+15+f+accent]; } if (accent==1) { //si il y a des accents try { return new String(lyricsBytes, "UTF-16"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); return new String(lyricsBytes); } } else { return new String(lyricsBytes); } } } else return ""; } //*********** //********************************* Get procedures *********************************************** public String GetTag(String filePath,String Tag){ byte[] bytesOfAudio = TagBytes(filePath); int positionTag = chercheTagChaine(bytesOfAudio, Tag); if (positionTag == 0) { return ""; } else { int longueurDuTag=(bytesOfAudio[positionTag+7]+256)%256+((bytesOfAudio[positionTag+6]+256)%256)*256; byte[] tagBytes=new byte[longueurDuTag-1]; for (int f = 0; f < longueurDuTag-1; f++) { //copie des octets du tag if(positionTag+11+f1) { //si longueur <=1 pas de chaine... byte[] tagBytes = new byte[longueurDuTag - 1]; for (int f = 0; f < longueurDuTag - 1; f++) { //copie des octets du tag if (positionTag + 11 + f < bytesOfAudio.length) { tagBytes[f] = bytesOfAudio[positionTag + 11 + f]; } } if (bytesOfAudio[positionTag + 11] == -1) { try { return new String(tagBytes, "UTF-16"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); return new String(tagBytes); } } else { return new String(tagBytes); } }else{return"";} } } public int GetPopmTag(String filePath,String Tag){ //specifique du à la structure du tag POPM byte[] bytesOfAudio = TagBytes(filePath); int positionTag = chercheTagChaine(bytesOfAudio, Tag); if (positionTag == 0) { return -1; } else { int longueurDuTag=bytesOfAudio[positionTag+7]; int n=0; while (bytesOfAudio[positionTag+10+n]!=0 && n enregistrement"); bytesToFile(bytesModifies, filePath); } } byte[] changeLongueurTotalDesTag(int ecart,byte[] longueurActuelle){ byte[] nouvelleLongueuerTotale=new byte[4]; int lgActuelle=longueurActuelle[0]*128*128*128+longueurActuelle[1]*128*128+longueurActuelle[2]*128+longueurActuelle[3]; Log.d("*** mylog ***", "lgActuelle " + lgActuelle+ " "+longueurActuelle[0]+ " "+longueurActuelle[1]+ " "+longueurActuelle[2]+ " "+longueurActuelle[3]); int nouvelleLongueur=lgActuelle+ecart; byte premierOctet = (byte) (nouvelleLongueur / (128 * 128*128)); //128 car codé sur 7 bits, le 8eme étant à 0 byte deuxiemeOctet = (byte) ((nouvelleLongueur - premierOctet * 128 * 128*128) / (128*128)); byte troisiemeOctet = (byte) ((nouvelleLongueur - premierOctet * 128 * 128*128 - deuxiemeOctet * 128*128)/128); byte quatriemeOctet = (byte) (nouvelleLongueur - premierOctet * 128 * 128*128 - deuxiemeOctet * 128*128 - troisiemeOctet*128); nouvelleLongueuerTotale[0]=premierOctet; nouvelleLongueuerTotale[1]=deuxiemeOctet; nouvelleLongueuerTotale[2]=troisiemeOctet; nouvelleLongueuerTotale[3]=quatriemeOctet; Log.d("*** mylog ***", "nouvelleLongueur " + nouvelleLongueur+ " "+nouvelleLongueuerTotale[0]+ " "+nouvelleLongueuerTotale[1]+ " "+nouvelleLongueuerTotale[2]+ " "+nouvelleLongueuerTotale[3]); return nouvelleLongueuerTotale; } void replaceTag(String Tag,String filePath ,String newTag) throws IOException { test="replaceTag"; byte[] bytesOfAudioOrigine = getByteArrayFromAudio(filePath); Log.d("*** mylog ***", "longueur Fichier Origine " + bytesOfAudioOrigine.length); int positionTagOrigine = chercheTagChaine(bytesOfAudioOrigine, Tag); if (positionTagOrigine == 0) { createTag(Tag,filePath); } byte[] bytesOfAudio = getByteArrayFromAudio(filePath); //recharge le fichier au cas où j'ai créée le Tag dans le if précédent sinon pb de longueur de Array Log.d("*** mylog ***", "longueur Fichier avant remplacement Tag " + bytesOfAudio.length); int positionTag = chercheTagChaine(bytesOfAudio, Tag); int longueurTagActuel = ((bytesOfAudio[positionTag + 7]+256)%256) + ((bytesOfAudio[positionTag + 6]+256)%256) * 256 + ((bytesOfAudio[positionTag + 5]+256)%256) * 256 * 256; //longueur codée sur 3 octets Log.d("*** mylog ***", "longueurTagActuel " + longueurTagActuel); int ecartLongueurTag = newTag.length()+1 - longueurTagActuel; byte[]longueurTotalTag=new byte[]{bytesOfAudio[6],bytesOfAudio[7],bytesOfAudio[8],bytesOfAudio[9]}; longueurTotalTag=changeLongueurTotalDesTag(ecartLongueurTag,longueurTotalTag); byte[] bytesModifies = new byte[bytesOfAudio.length + ecartLongueurTag]; for (int f = 0; f < 6; f++) { //copie des octets 6 1ers octects du header soit de 0 à 4 (ID3 et version et flag) bytesModifies[f] = bytesOfAudio[f]; } for (int f = 0; f <4 ; f++) { //copie des octets 4 octects de la nouvelle longueur totale des Tags soit de 6 à 9 bytesModifies[6+f] = longueurTotalTag[f]; } for (int f = 10; f < positionTag + 4; f++) { //copie des octets jusqu'aux 1er octets de longueur compris (le 4 ) //0-3 Frame identifier bytesModifies[f] = bytesOfAudio[f]; } byte premierOctet = (byte) ((newTag.length()+1) / (256 * 256)); //ajouter 1 car After the header always one Byte with value 00 follows and then begins frame body. Size has to include this Byte. byte deuxiemeOctet = (byte) (((newTag.length()+1) - premierOctet * 256 * 256) / 256); byte troisiemeOctet = (byte) ((newTag.length()+1) - premierOctet * 256 * 256 - deuxiemeOctet * 256); bytesModifies[positionTag + 5] = premierOctet; //4-7 Size le 4 est copié plus haut bytesModifies[positionTag + 6] = deuxiemeOctet; bytesModifies[positionTag + 7] = troisiemeOctet; bytesModifies[positionTag + 8] = bytesOfAudio[positionTag + 8]; //8-9 Flags bytesModifies[positionTag + 9] = bytesOfAudio[positionTag + 9]; if (Tag=="COMM"){ //specifique au commentaire bytesModifies[positionTag + 11]=(byte)"fra".charAt(0); bytesModifies[positionTag + 12]=(byte)"fra".charAt(1); bytesModifies[positionTag + 13]=(byte)"fra".charAt(2); bytesModifies[positionTag + 14]=0; for (int f = 0; f < newTag.length()-4; f++) { //copie du nouveau tag bytesModifies[positionTag + 15 + f] = (byte) newTag.charAt(f+4); } }else{ for (int f = 0; f < newTag.length(); f++) { //copie du nouveau tag bytesModifies[positionTag + 11 + f] = (byte) newTag.charAt(f); //11 car le 10 est null } } for (int f = 0; f < bytesOfAudio.length - (positionTag + 10 + longueurTagActuel); f++) { //copie du reste du fichier bytesModifies[positionTag + 11 + newTag.length() + f] = bytesOfAudio[positionTag + 10 + longueurTagActuel + f]; } test="fin createTag"; bytesToFile(bytesModifies, filePath); } void replaceImage(String Tag,String filePath ,byte[] newTag) throws IOException { byte[] bytesOfAudioOrigine = getByteArrayFromAudio(filePath); Log.d("*** mylog ***", "longueur Fichier Origine " + bytesOfAudioOrigine.length); int positionTagOrigine = chercheTagChaine(bytesOfAudioOrigine, Tag); if (positionTagOrigine == 0) { createTag(Tag,filePath); } byte[] bytesOfAudio = getByteArrayFromAudio(filePath); //recharge le fichier au cas où j'ai créée le Tag dans le if précédent sinon pb de longueur de Array Log.d("*** mylog ***", "longueur Fichier avant remplacement Tag " + bytesOfAudio.length); int positionTag = chercheTagChaine(bytesOfAudio, Tag); int longueurTagActuel = ((bytesOfAudio[positionTag + 7]+256)%256) + ((bytesOfAudio[positionTag + 6]+256)%256) * 256 + ((bytesOfAudio[positionTag + 5]+256)%256) * 256 * 256; //longueur codée sur 3 octets Log.d("*** mylog ***", "longueurTagActuel " + longueurTagActuel); int ecartLongueurTag = newTag.length+1+13 - longueurTagActuel; byte[]longueurTotalTag=new byte[]{bytesOfAudio[6],bytesOfAudio[7],bytesOfAudio[8],bytesOfAudio[9]}; longueurTotalTag=changeLongueurTotalDesTag(ecartLongueurTag,longueurTotalTag); byte[] bytesModifies = new byte[bytesOfAudio.length + ecartLongueurTag]; for (int f = 0; f < 6; f++) { //copie des octets 6 1ers octects du header soit de 0 à 4 (ID3 et version et flag) bytesModifies[f] = bytesOfAudio[f]; } for (int f = 0; f <4 ; f++) { //copie des octets 4 octects de la nouvelle longueur totale des Tags soit de 6 à 9 bytesModifies[6+f] = longueurTotalTag[f]; } for (int f = 10; f < positionTag + 4; f++) { //copie des octets jusqu'aux 1er octets de longueur compris (le 4 ) //0-3 Frame identifier bytesModifies[f] = bytesOfAudio[f]; } byte premierOctet = (byte) ((newTag.length+1) / (256 * 256)); //ajouter 1 car After the header always one Byte with value 00 follows and then begins frame body. Size has to include this Byte. byte deuxiemeOctet = (byte) (((newTag.length+1) - premierOctet * 256 * 256) / 256); byte troisiemeOctet = (byte) ((newTag.length+1) - premierOctet * 256 * 256 - deuxiemeOctet * 256); bytesModifies[positionTag + 5] = premierOctet; //4-7 Size le 4 est copié plus haut bytesModifies[positionTag + 6] = deuxiemeOctet; bytesModifies[positionTag + 7] = troisiemeOctet; bytesModifies[positionTag + 8] = bytesOfAudio[positionTag + 8]; //8-9 Flags bytesModifies[positionTag + 9] = bytesOfAudio[positionTag + 9]; for (int t = 0; t < 10; t++){ bytesModifies[positionTag + 11+t]=(byte)"image/jpeg".charAt(0+t); } bytesModifies[positionTag + 22]=3; //défini l'image comme cover for (int f = 0; f < newTag.length; f++) { //copie du nouveau tag bytesModifies[positionTag + 24 + f] = (byte) newTag[f]; //11 car le 10 est null } for (int f = 0; f < bytesOfAudio.length - (positionTag + 10 + longueurTagActuel); f++) { //copie du reste du fichier bytesModifies[positionTag + 24 + newTag.length + f] = bytesOfAudio[positionTag + 10 + longueurTagActuel + f]; } bytesToFile(bytesModifies, filePath); } int chercheTagChaine(byte[] bytes,String Tag){ //renvoie la position du Tag int pos=0; Log.d("*** mylog ***", "recherche Tag "+ pos); while (pos