1 /*
2  * Cantata
3  *
4  * Copyright (c) 2011-2020 Craig Drummond <craig.p.drummond@gmail.com>
5  *
6  * ----
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; see the file COPYING.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  */
22 
23 #include "extractjob.h"
24 #include "device.h"
25 #include "support/utils.h"
26 #include "tags/tags.h"
27 #include "cdparanoia.h"
28 #include "gui/covers.h"
29 #include "mpd-interface/mpdconnection.h"
30 #include "gui/settings.h"
31 #include <QStringList>
32 #include <QProcess>
33 #include <QFile>
34 
35 const int ExtractJob::constWavHeaderSize=44; // ffmpeg uses 46 byte header?
36 
insertSize(unsigned char * data,qint32 size)37 static void insertSize(unsigned char *data, qint32 size)
38 {
39     data[0]=(size&0x000000ff);
40     data[1]=(size&0x0000ff00)>>8;
41     data[2]=(size&0x00ff0000)>>16;
42     data[3]=(size&0xff000000)>>24;
43 }
44 
writeWavHeader(QIODevice & dev,qint32 size)45 void ExtractJob::writeWavHeader(QIODevice &dev, qint32 size)
46 {
47     unsigned char riffHeader[] = {
48         0x52, 0x49, 0x46, 0x46, // 0  "RIFF"
49         0x00, 0x00, 0x00, 0x00, // 4  wavSize
50         0x57, 0x41, 0x56, 0x45, // 8  "WAVE"
51         0x66, 0x6d, 0x74, 0x20, // 12 "fmt "
52         0x10, 0x00, 0x00, 0x00, // 16 Size of WAVE section chunk  (ffmpeg has 12 here???)
53         0x01, 0x00, 0x02, 0x00, // 20 WAVE type format / Number of channels
54         0x44, 0xac, 0x00, 0x00, // 24 Samples per second
55         0x10, 0xb1, 0x02, 0x00, // 28 Bytes per second
56         0x04, 0x00, 0x10, 0x00, // 32 Block alignment / Bits per sample
57         0x64, 0x61, 0x74, 0x61, // 36 "data"  (ffmpeg preceeds this with two 0 bytes)
58         0x00, 0x00, 0x00, 0x00  // 40 byteCount
59     };
60 
61     if (0!=size) {
62         insertSize(&riffHeader[4], (size+constWavHeaderSize)-8);
63         insertSize(&riffHeader[40], size);
64     }
65 
66     dev.write((char*)riffHeader, constWavHeaderSize);
67 }
68 
ExtractJob(const Encoders::Encoder & enc,int val,const QString & src,const QString & dest,const Song & s,const QString & cover)69 ExtractJob::ExtractJob(const Encoders::Encoder &enc, int val, const QString &src, const QString &dest, const Song &s, const QString &cover)
70     : encoder(enc)
71     , value(val)
72     , srcFile(src)
73     , destFile(dest)
74     , song(s)
75     , coverFile(cover)
76     , copiedCover(false)
77 {
78 }
79 
~ExtractJob()80 ExtractJob::~ExtractJob()
81 {
82 }
83 
run()84 void ExtractJob::run()
85 {
86     if (stopRequested) {
87         emit result(Device::Cancelled);
88     } else {
89         QStringList encParams=encoder.params(value, encoder.transcoder ? "pipe:" : "-", destFile);
90         CdParanoia cdparanoia(srcFile, Settings::self()->paranoiaFull(), Settings::self()->paranoiaNeverSkip(), false, Settings::self()->paranoiaOffset());
91 
92         if (!cdparanoia) {
93             emit result(Device::FailedToLockDevice);
94             return;
95         }
96         QProcess process;
97         QString cmd=encParams.takeFirst();
98         process.start(cmd, encParams, QIODevice::WriteOnly);
99         process.waitForStarted();
100 
101         if (stopRequested) {
102             emit result(Device::Cancelled);
103             process.close();
104             return;
105         }
106         int firstSector = cdparanoia.firstSectorOfTrack(song.id);
107         int lastSector = cdparanoia.lastSectorOfTrack(song.id);
108         int total=lastSector-firstSector;
109         int count=0;
110 
111         cdparanoia.seek(firstSector, SEEK_SET);
112 
113         writeWavHeader(process);
114         while ((firstSector+count) <= lastSector) {
115             qint16 *buf = cdparanoia.read();
116             if (!buf) {
117                 emit result(Device::Failed);
118                 process.close();
119                 return;
120             }
121             if (stopRequested) {
122                 emit result(Device::Cancelled);
123                 process.close();
124                 return;
125             }
126 
127             char *buffer=(char *)buf;
128 
129             qint64 writePos=0;
130             do {
131                 qint64 bytesWritten = process.write(&buffer[writePos], CD_FRAMESIZE_RAW - writePos);
132                 if (stopRequested) {
133                     emit result(Device::Cancelled);
134                     process.close();
135                     QFile::remove(destFile);
136                     return;
137                 }
138                 if (-1==bytesWritten) {
139                     emit result(Device::WriteFailed);
140                     process.close();
141                     QFile::remove(destFile);
142                     return;
143                 }
144                 writePos+=bytesWritten;
145             } while (writePos<CD_FRAMESIZE_RAW);
146 
147             count++;
148             setPercent((count*100/total)+0.5);
149         }
150         process.closeWriteChannel();
151         process.waitForFinished();
152         Utils::setFilePerms(destFile);
153         Tags::update(destFile, Song(), song, 3);
154 
155         if (!stopRequested && !coverFile.isEmpty()) {
156             copiedCover=Covers::copyImage(Utils::getDir(coverFile), Utils::getDir(destFile), Utils::getFile(coverFile), Covers::albumFileName(song)+Utils::getExtension(coverFile), 0);
157         }
158 
159         emit result(Device::Ok);
160     }
161 }
162 
163 #include "moc_extractjob.cpp"
164