Source

libtaginfo / libtaginfo / xiphinfo.cc

Full commit
/*
 * Copyright (C) 2008-2013 J.Rios <anonbeat@gmail.com>
 * Copyright (C) 2013 Jörn Magens
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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; see the file LICENSE.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth 
 * Floor, Boston, MA  02110-1301  USA
 * https://www.gnu.org/licenses/lgpl-2.1.txt
 *
 * Author:
 * 	Jörn Magens <shuerhaaken@googlemail.com>
 * 	Matias De lellis <mati86dl@gmail.com>
 * 	Pavel Vasin <rat4vier@gmail.com>
 */


#include "taginfo.h"
#include "taginfo_internal.h"
#include "taginfo_xiphtags.h"


#include <string>
#include <xiphcomment.h>



using namespace TagInfo;





static const string base64_char_string = 
             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
             "abcdefghijklmnopqrstuvwxyz"
             "0123456789+/";


static inline bool is_base64(unsigned char c) {
  return (isalnum(c) || (c == '+') || (c == '/'));
}


inline string base64_decode(const char* encoded_string) {
    int in_len = strlen(encoded_string);// encoded_string.size();
    int i = 0;
    int j = 0;
    int in_ = 0;
    unsigned char char_array_4[4], char_array_3[3];
    string ret;
    
    while(in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
        char_array_4[i++] = encoded_string[in_]; in_++;
        if (i ==4) {
        for (i = 0; i <4; i++)
            char_array_4[i] = base64_char_string.find(char_array_4[i]);
        
        char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
        char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
        char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
        
        for (i = 0; (i < 3); i++)
            ret += char_array_3[i];
            i = 0;
        }
    }
    
    if(i) {
        for (j = i; j <4; j++)
            char_array_4[j] = 0;
        
        for (j = 0; j <4; j++)
            char_array_4[j] = base64_char_string.find(char_array_4[j]);
        
        char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
        char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
        char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
        
        for (j = 0; (j < i - 1); j++)
            ret += char_array_3[j];
    }
    return ret;
}


static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char base64_pad = '=';

inline int base64encode_internal(const char * src, const size_t srclen, char * dst, const size_t dstlen) {
    if(((srclen / 3) + ((srclen % 3) > 0)) * 4 > dstlen)
        return -1;
    
    unsigned int tmp;
    const unsigned char * dat = ( unsigned char * ) src;
    int OutPos = 0;
    for(int i = 0; i < ( int ) srclen / 3; i++) {
        tmp  = (*dat++) << 16;
        tmp |= ((*dat++) <<  8);
        tmp |= (*dat++);
        dst[ OutPos++ ] = base64_chars[(tmp & 0x00FC0000 ) >> 18];
        dst[ OutPos++ ] = base64_chars[(tmp & 0x0003F000 ) >> 12];
        dst[ OutPos++ ] = base64_chars[(tmp & 0x00000FC0 ) >>  6];
        dst[ OutPos++ ] = base64_chars[(tmp & 0x0000003F )      ];
    }
    switch( srclen % 3 ) {
        case 1 :
            tmp = (* dat++) << 16;
            dst[OutPos++] = base64_chars[(tmp & 0x00FC0000 ) >> 18];
            dst[OutPos++] = base64_chars[(tmp & 0x0003F000 ) >> 12];
            dst[OutPos++] = base64_pad;
            dst[OutPos++] = base64_pad;
            break;
        case 2 :
            tmp  = (*dat++) << 16;
            tmp += (*dat++) <<  8;
            dst[OutPos++] = base64_chars[(tmp & 0x00FC0000 ) >> 18];
            dst[OutPos++] = base64_chars[(tmp & 0x0003F000 ) >> 12];
            dst[OutPos++] = base64_chars[(tmp & 0x00000FC0 ) >>  6];
            dst[OutPos++] = base64_pad;
            break;
    }
    return OutPos;
}

inline String base64encode(const char* src, const size_t srclen) {
    String RetVal;
    int dstlen = ((srclen / 3) + ((srclen % 3) > 0)) * 4;
    char * dst = (char *) malloc(dstlen);
    if(base64encode_internal(src, srclen, dst, dstlen) > 0) {
        ByteVector vect(dst, dstlen);
        RetVal = String(vect);
    }
    free(dst);
    return RetVal;
}


void check_xiph_label_frame(Ogg::XiphComment * xiphcomment, 
                                 const char * description, 
                                 const String &value) {
    if(xiphcomment->fieldListMap().contains(description)) {
            if(!value.isEmpty()) {
            xiphcomment->addField(description, value);
        }
        else {
            xiphcomment->removeField(description);
        }
    }
    else {
            if(!value.isEmpty()) {
            xiphcomment->addField(description, value);
        }
    }
}


XiphInfo::XiphInfo(const String &filename) : Info(filename) {
    xiphcomment = NULL;
}


XiphInfo::~XiphInfo() {
}



bool XiphInfo::load(void) {
    if(Info::load()) {
        if(xiphcomment && !xiphcomment->isEmpty()) {
            if(!taglib_tag)
                load_base_tags((TagLib::Tag *)xiphcomment);
            
            if(xiphcomment->fieldListMap().contains("COMPOSER")) {
                composer = xiphcomment->fieldListMap()["COMPOSER"].front();
            }
            if(xiphcomment->fieldListMap().contains("PERFORMER")) {
                original_artist = xiphcomment->fieldListMap()["PERFORMER"].front();
            }
            if(xiphcomment->fieldListMap().contains("DISCNUMBER")) {
                volume_number = atol(xiphcomment->fieldListMap()["DISCNUMBER"].front().toCString(false));
            }
            if(xiphcomment->fieldListMap().contains("DISCTOTAL")) {
                volume_count = atol(xiphcomment->fieldListMap()["DISCTOTAL"].front().toCString(false));
            }
            if(xiphcomment->fieldListMap().contains("TRACKTOTAL")) {
                track_count = atol(xiphcomment->fieldListMap()["TRACKTOTAL"].front().toCString(false));
            }
            if(xiphcomment->fieldListMap().contains("COMPILATION")) {
                is_compilation = xiphcomment->fieldListMap()["COMPILATION"].front() == String("1");
            }
            if(xiphcomment->fieldListMap().contains("ALBUMARTIST")) {
                album_artist = xiphcomment->fieldListMap()["ALBUMARTIST"].front();
            }
            else if(xiphcomment->fieldListMap().contains("ALBUM ARTIST")) {
                album_artist = xiphcomment->fieldListMap()["ALBUM ARTIST"].front();
            }
            if(xiphcomment->fieldListMap().contains("COPYRIGHT")) {
                copyright = xiphcomment->fieldListMap()["COPYRIGHT"].front();
            }
            if(xiphcomment->fieldListMap().contains("ENCODED-BY")) {
                encoder = xiphcomment->fieldListMap()["ENCODED-BY"].front();
            }
            if(xiphcomment->fieldListMap().contains("LICENSE")) {
                homepage = xiphcomment->fieldListMap()["LICENSE"].front();
            }
            // Rating
            if(xiphcomment->fieldListMap().contains("RATING")) {
                long rat = 0;
                rat = atol(xiphcomment->fieldListMap()["RATING"].front().toCString(true));
                if(rat) {
                    if(rat > 5) {
                        rating = popularity_to_rating(rat);
                    }
                    else {
                        rating = rat;
                    }
                }
            }
            if(xiphcomment->fieldListMap().contains("PLAY_COUNTER")) {
                long PlayCount = 0;
                PlayCount = atol(xiphcomment->fieldListMap()["PLAY_COUNTER"].front().toCString(true));
                playcount = PlayCount;
            }
            // Labels
            if(track_labels.size() == 0) {
                if(xiphcomment->fieldListMap().contains("TRACK_LABELS")) {
                    track_labels_string = xiphcomment->fieldListMap()["TRACK_LABELS"].front();
                    track_labels = split(track_labels_string, "|");
                }
            }
            if(artist_labels.size() == 0) {
                if(xiphcomment->fieldListMap().contains("ARTIST_LABELS")) {
                    artist_labels_string = xiphcomment->fieldListMap()["ARTIST_LABELS"].front();
                    artist_labels = split(artist_labels_string, "|");
                }
            }
            if(album_labels.size() == 0) {
                if(xiphcomment->fieldListMap().contains("ALBUM_LABELS")) {
                    album_labels_string = xiphcomment->fieldListMap()["ALBUM_LABELS"].front();
                    album_labels = split(album_labels_string, "|");
                }
            }
            if(xiphcomment->contains("COVERART")) // TODO
                has_image = true;
            return true;
        }
    }
    return false;
}


bool XiphInfo::save(void) {
    if(xiphcomment) {
        if(changedflag) {
            if(changedflag & CHANGED_DATA_VOL_NUM)
                xiphcomment->addField("DISCNUMBER", format("%u", volume_number), true);
            if(changedflag & CHANGED_DATA_VOL_CNT)
                xiphcomment->addField("DISCTOTAL", format("%u", volume_count), true);
            if(changedflag & CHANGED_COMPOSER_TAG)
                xiphcomment->addField("COMPOSER", composer, true);
            if(changedflag & CHANGED_ORIGINALARTIST_TAG)
                xiphcomment->addField("PERFORMER", original_artist, true);
            if(changedflag & CHANGED_TRACK_COUNT)
                xiphcomment->addField("TRACKTOTAL", format("%d", track_count), true);
            if(changedflag & CHANGED_IS_COMPILATION_TAG) {
                if(is_compilation) {
                    xiphcomment->addField("COMPILATION", "1", true);
                }
                else {
                    xiphcomment->addField("COMPILATION", "0", true);
                }
            }
            if(changedflag & CHANGED_DATA_ALBUMARTIST)
                xiphcomment->addField("ALBUMARTIST", album_artist, true);
            if(changedflag & CHANGED_DATA_RATING)
                xiphcomment->addField("RATING", format("%u", rating_to_popularity(rating)), true);
            if(changedflag & CHANGED_DATA_PLAYCOUNT)
                xiphcomment->addField("PLAY_COUNTER", format("%u", playcount), true);
            
            // The Labels
            if(changedflag & CHANGED_TRACK_LABELS)
                check_xiph_label_frame(xiphcomment, "TRACK_LABELS", track_labels_string);
            if(changedflag & CHANGED_ARTIST_LABELS)
                check_xiph_label_frame(xiphcomment, "ARTIST_LABELS", artist_labels_string);
            if(changedflag & CHANGED_ALBUM_LABELS)
                check_xiph_label_frame(xiphcomment, "ALBUM_LABELS", album_labels_string);
            
            if(changedflag & CHANGED_COPYRIGHT_TAG)
                xiphcomment->addField("COPYRIGHT", copyright, true);
            if(changedflag & CHANGED_ENCODER_TAG)
                xiphcomment->addField("ENCODED-BY", encoder, true);
            if(changedflag & CHANGED_HOMEPAGE_TAG)
                xiphcomment->addField("LICENSE", homepage, true);
            
            save_base_tags((TagLib::Tag *)xiphcomment);
        }
    }
    return Info::save();
}


Image ** XiphInfo::get_images(int &image_count) const {
    image_count = 0;
    if(xiphcomment && xiphcomment->contains("COVERART")) {
        Image** images = new Image*[1];
        
        Image * image = new Image();
        image->image_type = IMAGE_TYPE_COVER_FRONT;
        String mimetype = xiphcomment->fieldListMap()[ "COVERARTMIME" ].front().to8Bit(false);
        if(mimetype.find("/jpeg") != -1 || mimetype.find("/jpg") != -1)
            image->image_file_type = IMAGE_FILE_TYPE_JPEG;
        else if(mimetype.find("/png") != -1)
            image->image_file_type = IMAGE_FILE_TYPE_PNG;
        
        const char* CoverEncData = xiphcomment->fieldListMap()[ "COVERART" ].front().toCString(true); 
        
        string CoverDecData = base64_decode(CoverEncData);
        
        image->data_length = CoverDecData.size();
        image->data = new char[image->data_length];
        memcpy(image->data, CoverDecData.data(), CoverDecData.size());
        
        if(image->data && image->data_length > 0) {
            images[0] = image;
            image_count = 1;
            return images;
        }
    }
    return NULL;
}

void XiphInfo::set_images(const Image ** images, const int image_count) {
    if(xiphcomment) {
        if(xiphcomment->contains("COVERART")) {
            xiphcomment->removeField("COVERARTMIME");
            xiphcomment->removeField("COVERART");
        }
        for(int p = 0; p < image_count; p++) {
            const Image * image = images[p];
            if(image && image->data && image->data_length > 0) {
                if(image->image_file_type == IMAGE_FILE_TYPE_UNKNOWN || image->image_file_type == IMAGE_FILE_TYPE_JPEG)
                    xiphcomment->addField("COVERARTMIME", "image/jpeg");
                else if(image->image_file_type == IMAGE_FILE_TYPE_PNG)
                    xiphcomment->addField("COVERARTMIME", "image/png");
                
                xiphcomment->addField("COVERART", 
                                      base64encode(image->data, image->data_length).toCString(false));
            }
        }
    }
}


String XiphInfo::get_lyrics(void) const {
    if(xiphcomment && xiphcomment->contains("LYRICS")) {
        return xiphcomment->fieldListMap()[ "LYRICS" ].front();
    }
    return String();
}


bool XiphInfo::set_lyrics(const String &lyrics) {
    if(xiphcomment) {
        while(xiphcomment->contains("LYRICS")) {
            xiphcomment->removeField("LYRICS");
        }
        if(!lyrics.isEmpty()) {
            xiphcomment->addField("LYRICS", lyrics);
        }
        return true;
    }
    return false;
}