1 /*
2     SPDX-FileCopyrightText: 1998-2008 Sebastian Trueg <trueg@k3b.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "k3bcdtext.h"
8 #include "k3bcrc.h"
9 
10 #include <config-k3b.h>
11 
12 #include <QDebug>
13 #include <QSharedData>
14 #include <QTextCodec>
15 
16 #include <string.h>
17 
18 
19 namespace {
20 
21     struct cdtext_pack {
22         unsigned char id1;
23         unsigned char id2;
24         unsigned char id3;
25 #ifdef WORDS_BIGENDIAN // __BYTE_ORDER == __BIG_ENDIAN
26         unsigned char dbcc:       1;
27         unsigned char blocknum:   3;
28         unsigned char charpos:    4;
29 #else
30         unsigned char charpos:    4;
31         unsigned char blocknum:   3;
32         unsigned char dbcc:       1;
33 #endif
34         unsigned char data[12];
35         unsigned char crc[2];
36     };
37 
38     /**
39      * This one is taken from cdrecord
40      */
41     struct text_size_block {
42         char charcode;
43         char first_track;
44         char last_track;
45         char copyr_flags;
46         char pack_count[16];
47         char last_seqnum[8];
48         char language_codes[8];
49     };
50 
debugRawTextPackData(const unsigned char * data,int dataLen)51     void debugRawTextPackData( const unsigned char* data, int dataLen )
52     {
53         qDebug() << endl << " id1    | id2    | id3    | charps | blockn | dbcc | data           | crc |";
54 
55         cdtext_pack* pack = (cdtext_pack*)data;
56 
57         for( int i = 0; i < dataLen/18; ++i ) {
58             QString s;
59             s += QString( " %1 |" ).arg( pack[i].id1, 6, 16 );
60             s += QString( " %1 |" ).arg( pack[i].id2, 6 );
61             s += QString( " %1 |" ).arg( pack[i].id3, 6 );
62             s += QString( " %1 |" ).arg( pack[i].charpos, 6 );
63             s += QString( " %1 |" ).arg( pack[i].blocknum, 6 );
64             s += QString( " %1 |" ).arg( pack[i].dbcc, 4 );
65 //       char str[12];
66 //       sprintf( str, "%c%c%c%c%c%c%c%c%c%c%c%c",
67 // 	       pack[i].data[0] == '\0' ? '�' : pack[i].data[0],
68 // 	       pack[i].data[1] == '\0' ? '�' : pack[i].data[1],
69 // 	       pack[i].data[2] == '\0' ? '�' : pack[i].data[2],
70 // 	       pack[i].data[3] == '\0' ? '�' : pack[i].data[3],
71 // 	       pack[i].data[4] == '\0' ? '�' : pack[i].data[4],
72 // 	       pack[i].data[5] == '\0' ? '�' : pack[i].data[5],
73 // 	       pack[i].data[6] == '\0' ? '�' : pack[i].data[6],
74 // 	       pack[i].data[7] == '\0' ? '�' : pack[i].data[7],
75 // 	       pack[i].data[8] == '\0' ? '�' : pack[i].data[8],
76 // 	       pack[i].data[9] == '\0' ? '�' : pack[i].data[9],
77 // 	       pack[i].data[10] == '\0' ? '�' : pack[i].data[10],
78 // 	       pack[i].data[11] == '\0' ? '�' : pack[i].data[11] );
79 //       s += QString( " %1 |" ).arg( "'" + QCString(str,13) + "'", 14 );
80 //       quint16 crc = pack[i].crc[0]<<8|pack[i].crc[1];
81 //       s += QString( " %1 |" ).arg( crc );
82             qDebug() << s;
83         }
84     }
85 
86     // TODO: remove this (see above)
fixup(QString & s)87     void fixup( QString& s )
88     {
89         s.replace( '/', "_" );
90         s.replace( '\"', "_" );
91     }
92 
savePack(cdtext_pack * pack,QByteArray & data,int & dataFill)93     void savePack( cdtext_pack* pack, QByteArray& data, int& dataFill )
94     {
95         // create CRC
96         quint16 crc = K3b::Device::calcX25( reinterpret_cast<unsigned char*>(pack), sizeof(cdtext_pack)-2 );
97 
98         // invert for Redbook compliance
99         crc ^= 0xffff;
100 
101         pack->crc[0] = (crc>>8) & 0xff;
102         pack->crc[1] = crc & 0xff;
103 
104 
105         // append the pack to data
106         if( data.size() < dataFill + ( int )sizeof(cdtext_pack) )
107             data.resize( dataFill + sizeof(cdtext_pack) );
108 
109         ::memcpy( &data.data()[dataFill], reinterpret_cast<char*>( pack ), sizeof(cdtext_pack) );
110 
111         dataFill += sizeof(cdtext_pack);
112     }
113 
114 
appendByteArray(QByteArray & a,const QByteArray & b)115     void appendByteArray( QByteArray& a, const QByteArray& b )
116     {
117         int oldSize = a.size();
118         a.resize( oldSize + b.size() );
119         ::memcpy( &a.data()[oldSize], b.data(), b.size() );
120     }
121 
122 
encodeCdText(const QString & s,bool * illegalChars=0)123     QByteArray encodeCdText( const QString& s, bool* illegalChars = 0 )
124     {
125         if( illegalChars )
126             *illegalChars = false;
127 
128         // TODO: do this without QT
129         QTextCodec* codec = QTextCodec::codecForName("ISO8859-1");
130         if( codec ) {
131             QByteArray encoded = codec->fromUnicode( s );
132             return encoded;
133         }
134         else {
135             QByteArray r( s.length()+1, 0 );
136 
137             for( int i = 0; i < s.length(); ++i ) {
138                 if( s[i].toLatin1() == 0 ) { // non-ASCII character
139                     r[i] = ' ';
140                     if( illegalChars )
141                         *illegalChars = true;
142                 }
143                 else
144                     r[i] = s[i].toLatin1();
145             }
146 
147             return r;
148         }
149     }
150 }
151 
152 
153 class K3b::Device::TrackCdText::Private : public QSharedData
154 {
155 public:
156     QString title;
157     QString performer;
158     QString songwriter;
159     QString composer;
160     QString arranger;
161     QString message;
162     QString isrc;
163 };
164 
165 
TrackCdText()166 K3b::Device::TrackCdText::TrackCdText()
167     : d( new Private() )
168 {
169 }
170 
171 
TrackCdText(const TrackCdText & other)172 K3b::Device::TrackCdText::TrackCdText( const TrackCdText& other )
173 {
174     d = other.d;
175 }
176 
177 
~TrackCdText()178 K3b::Device::TrackCdText::~TrackCdText()
179 {
180 }
181 
182 
operator =(const TrackCdText & other)183 K3b::Device::TrackCdText& K3b::Device::TrackCdText::operator=( const TrackCdText& other )
184 {
185     d = other.d;
186     return *this;
187 }
188 
189 
clear()190 void K3b::Device::TrackCdText::clear()
191 {
192     d->title.truncate(0);
193     d->performer.truncate(0);
194     d->songwriter.truncate(0);
195     d->composer.truncate(0);
196     d->arranger.truncate(0);
197     d->message.truncate(0);
198     d->isrc.truncate(0);
199 }
200 
201 
title() const202 QString K3b::Device::TrackCdText::title() const
203 {
204     return d->title;
205 }
206 
207 
performer() const208 QString K3b::Device::TrackCdText::performer() const
209 {
210     return d->performer;
211 }
212 
213 
songwriter() const214 QString K3b::Device::TrackCdText::songwriter() const
215 {
216     return d->songwriter;
217 }
218 
219 
composer() const220 QString K3b::Device::TrackCdText::composer() const
221 {
222     return d->composer;
223 }
224 
225 
arranger() const226 QString K3b::Device::TrackCdText::arranger() const
227 {
228     return d->arranger;
229 }
230 
231 
message() const232 QString K3b::Device::TrackCdText::message() const
233 {
234     return d->message;
235 }
236 
237 
isrc() const238 QString K3b::Device::TrackCdText::isrc() const
239 {
240     return d->isrc;
241 }
242 
243 
setTitle(const QString & s)244 void K3b::Device::TrackCdText::setTitle( const QString& s )
245 {
246     d->title = s;
247     fixup(d->title);
248 }
249 
250 
setPerformer(const QString & s)251 void K3b::Device::TrackCdText::setPerformer( const QString& s )
252 {
253     d->performer = s;
254     fixup(d->performer);
255 }
256 
257 
setSongwriter(const QString & s)258 void K3b::Device::TrackCdText::setSongwriter( const QString& s )
259 {
260     d->songwriter = s;
261     fixup(d->songwriter);
262 }
263 
264 
setComposer(const QString & s)265 void K3b::Device::TrackCdText::setComposer( const QString& s )
266 {
267     d->composer = s;
268     fixup(d->composer);
269 }
270 
271 
setArranger(const QString & s)272 void K3b::Device::TrackCdText::setArranger( const QString& s )
273 {
274     d->arranger = s;
275     fixup(d->arranger);
276 }
277 
278 
setMessage(const QString & s)279 void K3b::Device::TrackCdText::setMessage( const QString& s )
280 {
281     d->message = s;
282     fixup(d->message);
283 }
284 
285 
setIsrc(const QString & s)286 void K3b::Device::TrackCdText::setIsrc( const QString& s )
287 {
288     d->isrc = s;
289     fixup(d->isrc);
290 }
291 
292 
isEmpty() const293 bool K3b::Device::TrackCdText::isEmpty() const
294 {
295     if( !d->title.isEmpty() )
296         return false;
297     if( !d->performer.isEmpty() )
298         return false;
299     if( !d->songwriter.isEmpty() )
300         return false;
301     if( !d->composer.isEmpty() )
302         return false;
303     if( !d->arranger.isEmpty() )
304         return false;
305     if( !d->message.isEmpty() )
306         return false;
307     if( !d->isrc.isEmpty() )
308         return false;
309 
310     return true;
311 }
312 
313 
operator ==(const K3b::Device::TrackCdText & other) const314 bool K3b::Device::TrackCdText::operator==( const K3b::Device::TrackCdText& other ) const
315 {
316     return( d->title == other.d->title &&
317             d->performer == other.d->performer &&
318             d->songwriter == other.d->songwriter &&
319             d->composer == other.d->composer &&
320             d->arranger == other.d->arranger &&
321             d->message == other.d->message &&
322             d->isrc == other.d->isrc );
323 }
324 
325 
operator !=(const K3b::Device::TrackCdText & other) const326 bool K3b::Device::TrackCdText::operator!=( const K3b::Device::TrackCdText& other ) const
327 {
328     return !operator==( other );
329 }
330 
331 
332 
333 // TODO: use the real CD-TEXT charset (a modified ISO8859-1)
334 
335 class K3b::Device::CdText::Private : public QSharedData
336 {
337 public:
Private()338     Private() {
339     }
340 
Private(const Private & other)341     Private( const Private& other )
342         : QSharedData( other ),
343           title(other.title),
344           performer(other.performer),
345           songwriter(other.songwriter),
346           composer(other.composer),
347           arranger(other.arranger),
348           message(other.message),
349           discId(other.discId),
350           upcEan(other.upcEan),
351           tracks(other.tracks) {
352         // do not copy rawData. it needs to be recreated if the cdtext changes
353     }
354 
355     QString title;
356     QString performer;
357     QString songwriter;
358     QString composer;
359     QString arranger;
360     QString message;
361     QString discId;
362     QString upcEan;
363 
364     QList<TrackCdText> tracks;
365 
366     mutable QByteArray rawData;
367 
368     QString textForPackType( int packType, int track ) const;
369     int textLengthForPackType( int packType ) const;
370     QByteArray createPackData( int packType, int& ) const;
371 };
372 
373 
CdText()374 K3b::Device::CdText::CdText()
375     : d( new Private() )
376 {
377 }
378 
379 
CdText(const K3b::Device::CdText & text)380 K3b::Device::CdText::CdText( const K3b::Device::CdText& text )
381 {
382     d = text.d;
383 }
384 
385 
CdText(const unsigned char * data,int len)386 K3b::Device::CdText::CdText( const unsigned char* data, int len )
387     : d( new Private() )
388 {
389     setRawPackData( data, len );
390 }
391 
392 
CdText(const QByteArray & b)393 K3b::Device::CdText::CdText( const QByteArray& b )
394     : d( new Private() )
395 {
396     setRawPackData( b );
397 }
398 
399 
~CdText()400 K3b::Device::CdText::~CdText()
401 {
402 }
403 
404 
operator =(const CdText & other)405 K3b::Device::CdText& K3b::Device::CdText::operator=( const CdText& other )
406 {
407     d = other.d;
408     return *this;
409 }
410 
411 
operator [](int i) const412 K3b::Device::TrackCdText K3b::Device::CdText::operator[]( int i ) const
413 {
414     return d->tracks[i];
415 }
416 
417 
operator [](int i)418 K3b::Device::TrackCdText& K3b::Device::CdText::operator[]( int i )
419 {
420     return track( i );
421 }
422 
423 
count() const424 int K3b::Device::CdText::count() const
425 {
426     return d->tracks.count();
427 }
428 
429 
track(int i) const430 K3b::Device::TrackCdText K3b::Device::CdText::track( int i ) const
431 {
432     return d->tracks[i];
433 }
434 
435 
track(int i)436 K3b::Device::TrackCdText& K3b::Device::CdText::track( int i )
437 {
438     while ( i >= d->tracks.count() ) {
439         d->tracks.append( TrackCdText() );
440     }
441     return d->tracks[i];
442 }
443 
444 
insert(int index,const TrackCdText & tt)445 void K3b::Device::CdText::insert( int index, const TrackCdText& tt )
446 {
447     d->tracks.insert( index, tt );
448 }
449 
450 
clear()451 void K3b::Device::CdText::clear()
452 {
453     d->tracks.clear();
454 
455     d->title.clear();
456     d->performer.clear();
457     d->songwriter.clear();
458     d->composer.clear();
459     d->arranger.clear();
460     d->message.clear();
461     d->discId.clear();
462     d->upcEan.clear();
463 }
464 
465 
empty() const466 bool K3b::Device::CdText::empty() const
467 {
468     if( !d->title.isEmpty() )
469         return false;
470     if( !d->performer.isEmpty() )
471         return false;
472     if( !d->songwriter.isEmpty() )
473         return false;
474     if( !d->composer.isEmpty() )
475         return false;
476     if( !d->arranger.isEmpty() )
477         return false;
478     if( !d->message.isEmpty() )
479         return false;
480     if( !d->discId.isEmpty() )
481         return false;
482     if( !d->upcEan.isEmpty() )
483         return false;
484 
485     for( int i = 0; i < count(); ++i )
486         if( !d->tracks.at(i).isEmpty() )
487             return false;
488 
489     return true;
490 }
491 
492 
isEmpty() const493 bool K3b::Device::CdText::isEmpty() const
494 {
495     return empty();
496 }
497 
498 
title() const499 QString K3b::Device::CdText::title() const
500 {
501     return d->title;
502 }
503 
504 
performer() const505 QString K3b::Device::CdText::performer() const
506 {
507     return d->performer;
508 }
509 
510 
songwriter() const511 QString K3b::Device::CdText::songwriter() const
512 {
513     return d->songwriter;
514 }
515 
516 
composer() const517 QString K3b::Device::CdText::composer() const
518 {
519     return d->composer;
520 }
521 
522 
arranger() const523 QString K3b::Device::CdText::arranger() const
524 {
525     return d->arranger;
526 }
527 
528 
message() const529 QString K3b::Device::CdText::message() const
530 {
531     return d->message;
532 }
533 
534 
discId() const535 QString K3b::Device::CdText::discId() const
536 {
537     return d->discId;
538 }
539 
540 
upcEan() const541 QString K3b::Device::CdText::upcEan() const
542 {
543     return d->upcEan;
544 }
545 
546 
setTitle(const QString & s)547 void K3b::Device::CdText::setTitle( const QString& s )
548 {
549     d->title = s;
550     fixup(d->title);
551 }
552 
553 
setPerformer(const QString & s)554 void K3b::Device::CdText::setPerformer( const QString& s )
555 {
556     d->performer = s;
557     fixup(d->performer);
558 }
559 
560 
setSongwriter(const QString & s)561 void K3b::Device::CdText::setSongwriter( const QString& s )
562 {
563     d->songwriter = s;
564     fixup(d->songwriter);
565 }
566 
567 
setComposer(const QString & s)568 void K3b::Device::CdText::setComposer( const QString& s )
569 {
570     d->composer = s;
571     fixup(d->composer);
572 }
573 
574 
setArranger(const QString & s)575 void K3b::Device::CdText::setArranger( const QString& s )
576 {
577     d->arranger = s;
578     fixup(d->arranger);
579 }
580 
581 
setMessage(const QString & s)582 void K3b::Device::CdText::setMessage( const QString& s )
583 {
584     d->message = s;
585     fixup(d->message);
586 }
587 
588 
setDiscId(const QString & s)589 void K3b::Device::CdText::setDiscId( const QString& s )
590 {
591     d->discId = s;
592     fixup(d->discId);
593 }
594 
595 
setUpcEan(const QString & s)596 void K3b::Device::CdText::setUpcEan( const QString& s )
597 {
598     d->upcEan = s;
599     fixup(d->upcEan);
600 }
601 
602 
setRawPackData(const unsigned char * data,int len)603 void K3b::Device::CdText::setRawPackData( const unsigned char* data, int len )
604 {
605     clear();
606 
607     int r = len%18;
608     if( r > 0 && r != 4 ) {
609         qDebug() << "(K3b::Device::CdText) invalid cdtext size: " << len;
610     }
611     else if( len-r > 0 ) {
612         debugRawTextPackData( &data[r], len-r );
613 
614         cdtext_pack* pack = (cdtext_pack*)&data[r];
615 
616 
617         for( int i = 0; i < (len-r)/18; ++i ) {
618 
619             if( pack[i].dbcc ) {
620                 qDebug() << "(K3b::Device::CdText) Double byte code not supported";
621                 return;
622             }
623 
624             //
625             // For some reason all crc bits are inverted.
626             //
627             pack[i].crc[0] ^= 0xff;
628             pack[i].crc[1] ^= 0xff;
629 
630             quint16 crc = calcX25( reinterpret_cast<unsigned char*>(&pack[i]), 18 );
631 
632             pack[i].crc[0] ^= 0xff;
633             pack[i].crc[1] ^= 0xff;
634 
635             if( crc != 0x0000 )
636                 qDebug() << "(K3b::Device::CdText) CRC invalid!";
637 
638 
639             //
640             // pack.data has a length of 12
641             //
642             // id1 tells us the tracknumber of the data (0 for global)
643             // data may contain multiple \0. In that case after every \0 the track number increases 1
644             //
645 
646             char* nullPos = (char*)pack[i].data - 1;
647 
648             int trackNo = pack[i].id2;
649 
650             while( nullPos ) {
651                 char* nextNullPos = (char*)::memchr( nullPos+1, '\0', 11 - (nullPos - (char*)pack[i].data) );
652                 QString txtstr;
653                 if( nextNullPos ) // take all chars up to the next null
654                     txtstr = QString::fromLatin1( (char*)nullPos+1, nextNullPos - nullPos - 1 );
655                 else // take all chars to the end of the pack data (12 bytes)
656                     txtstr = QString::fromLatin1( (char*)nullPos+1, 11 - (nullPos - (char*)pack[i].data) );
657 
658                 //
659                 // a tab character means to use the same as for the previous track
660                 //
661                 if( txtstr == "\t" )
662                     txtstr = d->textForPackType( pack[i].id1, trackNo-1 );
663 
664                 switch( pack[i].id1 ) {
665                 case 0x80: // Title
666                     if( trackNo == 0 )
667                         d->title.append( txtstr );
668                     else
669                         track(trackNo-1).d->title.append( txtstr );
670                     break;
671 
672                 case 0x81: // Performer
673                     if( trackNo == 0 )
674                         d->performer.append( txtstr );
675                     else
676                         track(trackNo-1).d->performer.append( txtstr );
677                     break;
678 
679                 case 0x82: // Writer
680                     if( trackNo == 0 )
681                         d->songwriter.append( txtstr );
682                     else
683                         track(trackNo-1).d->songwriter.append( txtstr );
684                     break;
685 
686                 case 0x83: // Composer
687                     if( trackNo == 0 )
688                         d->composer.append( txtstr );
689                     else
690                         track(trackNo-1).d->composer.append( txtstr );
691                     break;
692 
693                 case 0x84: // Arranger
694                     if( trackNo == 0 )
695                         d->arranger.append( txtstr );
696                     else
697                         track(trackNo-1).d->arranger.append( txtstr );
698                     break;
699 
700                 case 0x85: // Message
701                     if( trackNo == 0 )
702                         d->message.append( txtstr );
703                     else
704                         track(trackNo-1).d->message.append( txtstr );
705                     break;
706 
707                 case 0x86: // Disc identification
708                     // only global
709                     if( trackNo == 0 )
710                         d->discId.append( txtstr );
711                     break;
712 
713                 case 0x8e: // Upc or isrc
714                     if( trackNo == 0 )
715                         d->upcEan.append( txtstr );
716                     else
717                         track(trackNo-1).d->isrc.append( txtstr );
718                     break;
719 
720                     // TODO: support for binary data
721                     // 0x88: TOC
722                     // 0x89: second TOC
723                     // 0x8f: Size information
724 
725                 default:
726                     break;
727                 }
728 
729                 trackNo++;
730                 nullPos = nextNullPos;
731             }
732         }
733 
734         // remove empty fields at the end
735         while( !d->tracks.isEmpty() && d->tracks.last().isEmpty() ) {
736             d->tracks.removeLast();
737         }
738 
739         // we preserve the original data for clean 1-to-1 copies
740         d->rawData = QByteArray( reinterpret_cast<const char*>(data), len );
741     }
742     else
743         qDebug() << "(K3b::Device::CdText) zero-sized CD-TEXT: " << len;
744 }
745 
746 
setRawPackData(const QByteArray & b)747 void K3b::Device::CdText::setRawPackData( const QByteArray& b )
748 {
749     setRawPackData( reinterpret_cast<const unsigned char*>(b.data()), b.size() );
750 }
751 
rawPackData() const752 QByteArray K3b::Device::CdText::rawPackData() const
753 {
754     if( d->rawData.isEmpty() ) {
755         // FIXME: every pack block may only consist of up to 255 packs.
756 
757         int pc = 0;
758         int alreadyCountedPacks = 0;
759 
760 
761         //
762         // prepare the size information block
763         //
764         text_size_block tsize;
765         ::memset( &tsize, 0, sizeof(text_size_block) );
766         tsize.charcode = 0;              // ISO 8859-1
767         tsize.first_track = 1;
768         tsize.last_track = count();
769         tsize.pack_count[0xF] = 3;
770         tsize.language_codes[0] = 0x09;  // English (from cdrecord)
771 
772 
773         //
774         // create the CD-Text packs
775         //
776         QByteArray data;
777         for( int i = 0; i <= 6; ++i ) {
778             if( d->textLengthForPackType( 0x80 | i ) ) {
779                 appendByteArray( data, d->createPackData( 0x80 | i, pc ) );
780                 tsize.pack_count[i] = pc - alreadyCountedPacks;
781                 alreadyCountedPacks = pc;
782             }
783         }
784         if( d->textLengthForPackType( 0x8E ) ) {
785             appendByteArray( data, d->createPackData( 0x8E, pc ) );
786             tsize.pack_count[0xE] = pc - alreadyCountedPacks;
787             alreadyCountedPacks = pc;
788         }
789 
790 
791         // pc is the number of the next pack and we add 3 size packs
792         tsize.last_seqnum[0] = pc + 2;
793 
794 
795         //
796         // create the size info packs
797         //
798         int dataFill = data.size();
799         data.resize( data.size() + 3 * sizeof(cdtext_pack) );
800         for( int i = 0; i < 3; ++i ) {
801             cdtext_pack pack;
802             ::memset( &pack, 0, sizeof(cdtext_pack) );
803             pack.id1 = 0x8F;
804             pack.id2 = i;
805             pack.id3 = pc+i;
806             ::memcpy( pack.data, &reinterpret_cast<char*>(&tsize)[i*12], 12 );
807             savePack( &pack, data, dataFill );
808         }
809 
810         //
811         // add MMC header
812         //
813         QByteArray a( 4, 0 );
814         a[0] = (data.size()+2)>>8 & 0xff;
815         a[1] = (data.size()+2) & 0xff;
816         a[2] = a[3] = 0;
817         appendByteArray( a, data );
818 
819         d->rawData = a;
820     }
821 
822     return d->rawData;
823 }
824 
825 
826 // this method also creates completely empty packs
createPackData(int packType,int & packCount) const827 QByteArray K3b::Device::CdText::Private::createPackData( int packType, int& packCount ) const
828 {
829     QByteArray data;
830     int dataFill = 0;
831     QByteArray text = encodeCdText( textForPackType( packType, 0 ) );
832     int currentTrack = 0;
833     unsigned int textPos = 0;
834     unsigned int packPos = 0;
835 
836     //
837     // initialize the first pack
838     //
839     cdtext_pack pack;
840     ::memset( &pack, 0, sizeof(cdtext_pack) );
841     pack.id1 = packType;
842     pack.id3 = packCount;
843 
844     //
845     // We break this loop when all texts have been packed
846     //
847     while( 1 ) {
848         //
849         // Copy as many bytes as possible into the pack
850         //
851         size_t copyBytes = qMin(12 - packPos, text.length() - textPos);
852         if (copyBytes) {
853             ::memcpy(reinterpret_cast<char*>(&pack.data[packPos]),
854                      &text.data()[textPos], copyBytes );
855         }
856         textPos += copyBytes;
857         packPos += copyBytes;
858 
859 
860         //
861         // Check if the packdata is full
862         //
863         if( packPos > 11 ) {
864 
865             savePack( &pack, data, dataFill );
866             ++packCount;
867 
868             //
869             // reset the pack
870             //
871             ::memset( &pack, 0, sizeof(cdtext_pack) );
872             pack.id1 = packType;
873             pack.id2 = currentTrack;
874             pack.id3 = packCount;
875             packPos = 0;
876 
877             // update the charpos in case we continue a text in the next pack
878             if( textPos <= text.length() )
879                 pack.charpos = ( textPos > 15 ? 15 : textPos );
880         }
881 
882 
883         //
884         // Check if we have no text data left
885         //
886         if( textPos >= text.length() ) {
887 
888             // add one zero spacer byte
889             ++packPos;
890 
891             ++currentTrack;
892 
893             // Check if all texts have been packed
894             if( currentTrack > tracks.count() ) {
895                 savePack( &pack, data, dataFill );
896                 ++packCount;
897 
898                 data.resize( dataFill );
899                 return data;
900             }
901 
902             // next text block
903             text = encodeCdText( textForPackType( packType, currentTrack ) );
904             textPos = 0;
905         }
906     }
907 }
908 
909 
910 // track 0 means global cdtext
textForPackType(int packType,int track) const911 QString K3b::Device::CdText::Private::textForPackType( int packType, int track ) const
912 {
913     switch( packType ) {
914     default:
915     case 0x80:
916         if( track == 0 )
917             return title;
918         else if( track > 0 && track <= tracks.count() )
919             return tracks[track-1].title();
920         else
921             return QString();
922 
923     case 0x81:
924         if( track == 0 )
925             return performer;
926         else if( track > 0 && track <= tracks.count() )
927             return tracks[track-1].performer();
928         else
929             return QString();
930 
931     case 0x82:
932         if( track == 0 )
933             return songwriter;
934         else if( track > 0 && track <= tracks.count() )
935             return tracks[track-1].songwriter();
936         else
937             return QString();
938 
939     case 0x83:
940         if( track == 0 )
941             return composer;
942         else if( track > 0 && track <= tracks.count() )
943             return tracks[track-1].composer();
944         else
945             return QString();
946 
947     case 0x84:
948         if( track == 0 )
949             return arranger;
950         else if( track > 0 && track <= tracks.count() )
951             return tracks[track-1].arranger();
952         else
953             return QString();
954 
955     case 0x85:
956         if( track == 0 )
957             return message;
958         else if( track > 0 && track <= tracks.count() )
959             return tracks[track-1].message();
960         else
961             return QString();
962 
963     case 0x86:
964         if( track == 0 )
965             return discId;
966         else
967             return QString();
968 
969 //     case 0x87:
970 //         if( track == 0 )
971 //             return genre;
972 //         else if( track > 0 && track <= tracks.count() )
973 //             return tracks[track-1].title();
974 //         else
975 //             return QString();
976 
977     case 0x8E:
978         if( track == 0 )
979             return upcEan;
980         else if( track > 0 && track <= tracks.count() )
981             return tracks[track-1].isrc();
982         else
983             return QString();
984     }
985 }
986 
987 
988 // count the overall length of a certain packtype texts
textLengthForPackType(int packType) const989 int K3b::Device::CdText::Private::textLengthForPackType( int packType ) const
990 {
991     int len = 0;
992     for( int i = 0; i <= tracks.count(); ++i )
993         len += encodeCdText( textForPackType( packType, i ) ).length();
994     return len;
995 }
996 
997 
debug() const998 void K3b::Device::CdText::debug() const
999 {
1000     // debug the stuff
1001     qDebug() << "CD-TEXT data:" << endl
1002              << "Global:" << endl
1003              << "  Title:      '" << title() << "'" << endl
1004              << "  Performer:  '" << performer() << "'" << endl
1005              << "  Songwriter: '" << songwriter() << "'" << endl
1006              << "  Composer:   '" << composer() << "'" << endl
1007              << "  Arranger:   '" << arranger() << "'" << endl
1008              << "  Message:    '" << message() << "'" << endl
1009              << "  Disc ID:    '" << discId() << "'" << endl
1010              << "  Upc Ean:    '" << upcEan() << "'" << endl;
1011     for( int i = 0; i < count(); ++i ) {
1012         qDebug() << "Track " << (i+1) << ":" << endl
1013                  << "  Title:      '" << d->tracks[i].title() << "'" << endl
1014                  << "  Performer:  '" << d->tracks[i].performer() << "'" << endl
1015                  << "  Songwriter: '" << d->tracks[i].songwriter() << "'" << endl
1016                  << "  Composer:   '" << d->tracks[i].composer() << "'" << endl
1017                  << "  Arranger:   '" << d->tracks[i].arranger() << "'" << endl
1018                  << "  Message:    '" << d->tracks[i].message() << "'" << endl
1019                  << "  Isrc:       '" << d->tracks[i].isrc() << "'" << endl;
1020     }
1021 }
1022 
1023 
operator ==(const K3b::Device::CdText & other) const1024 bool K3b::Device::CdText::operator==( const K3b::Device::CdText& other ) const
1025 {
1026     return( d->title == other.d->title &&
1027             d->performer == other.d->performer &&
1028             d->songwriter == other.d->songwriter &&
1029             d->composer == other.d->composer &&
1030             d->arranger == other.d->arranger &&
1031             d->message == other.d->message &&
1032             d->discId == other.d->discId &&
1033             d->upcEan == other.d->upcEan &&
1034             d->tracks == other.d->tracks );
1035 }
1036 
1037 
operator !=(const K3b::Device::CdText & other) const1038 bool K3b::Device::CdText::operator!=( const K3b::Device::CdText& other ) const
1039 {
1040     return !operator==( other );
1041 }
1042 
1043 
checkCrc(const unsigned char * data,int len)1044 bool K3b::Device::CdText::checkCrc( const unsigned char* data, int len )
1045 {
1046     int r = len%18;
1047     if( r > 0 && r != 4 ) {
1048         qDebug() << "(K3b::Device::CdText) invalid cdtext size: " << len;
1049         return false;
1050     }
1051     else {
1052         len -= r;
1053 
1054         // TODO: what if the crc field is not used? All zeros?
1055 
1056         for( int i = 0; i < (len-r)/18; ++i ) {
1057             cdtext_pack* pack = (cdtext_pack*)&data[r];
1058 
1059             //
1060             // For some reason all crc bits are inverted.
1061             //
1062             pack[i].crc[0] ^= 0xff;
1063             pack[i].crc[1] ^= 0xff;
1064 
1065             int crc = calcX25( reinterpret_cast<unsigned char*>(&pack[i]), 18 );
1066 
1067             pack[i].crc[0] ^= 0xff;
1068             pack[i].crc[1] ^= 0xff;
1069 
1070             if( crc != 0x0000 )
1071                 return false;
1072         }
1073 
1074         return true;
1075     }
1076 }
1077 
1078 
checkCrc(const QByteArray & rawData)1079 bool K3b::Device::CdText::checkCrc( const QByteArray& rawData )
1080 {
1081     return checkCrc( reinterpret_cast<const unsigned char*>(rawData.data()), rawData.size() );
1082 }
1083