aboutsummaryrefslogtreecommitdiff
path: root/subsonic-main/src/main/java/net/sourceforge/subsonic/io/TranscodeInputStream.java
blob: b7a5e31e98b330bc4cfdae493234a4075c1d8ea2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/*
 This file is part of Subsonic.

 Subsonic is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 Subsonic 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 Subsonic.  If not, see <http://www.gnu.org/licenses/>.

 Copyright 2009 (C) Sindre Mehus
 */
package net.sourceforge.subsonic.io;

import net.sourceforge.subsonic.*;

import org.apache.commons.io.*;

import java.io.*;

/**
 * Subclass of {@link InputStream} which provides on-the-fly transcoding.
 * Instances of <code>TranscodeInputStream</code> can be chained together, for instance to convert
 * from OGG to WAV to MP3.
 *
 * @author Sindre Mehus
 */
public class TranscodeInputStream extends InputStream {

    private static final Logger LOG = Logger.getLogger(TranscodeInputStream.class);

    private InputStream processInputStream;
    private OutputStream processOutputStream;
    private Process process;
    private final File tmpFile;

    /**
     * Creates a transcoded input stream by executing an external process. If <code>in</code> is not null,
     * data from it is copied to the command.
     *
     * @param processBuilder Used to create the external process.
     * @param in Data to feed to the process.  May be {@code null}.
     * @param tmpFile Temporary file to delete when this stream is closed.  May be {@code null}.
     * @throws IOException If an I/O error occurs.
     */
    public TranscodeInputStream(ProcessBuilder processBuilder, final InputStream in, File tmpFile) throws IOException {
        this.tmpFile = tmpFile;

        StringBuffer buf = new StringBuffer("Starting transcoder: ");
        for (String s : processBuilder.command()) {
            buf.append('[').append(s).append("] ");
        }
        LOG.debug(buf);

        process = processBuilder.start();
        processOutputStream = process.getOutputStream();
        processInputStream = process.getInputStream();

        // Must read stderr from the process, otherwise it may block.
        final String name = processBuilder.command().get(0);
        new InputStreamReaderThread(process.getErrorStream(), name, true).start();

        // Copy data in a separate thread
        if (in != null) {
            new Thread(name + " TranscodedInputStream copy thread") {
                public void run() {
                    try {
                        IOUtils.copy(in, processOutputStream);
                    } catch (IOException x) {
                        // Intentionally ignored. Will happen if the remote player closes the stream.
                    } finally {
                        IOUtils.closeQuietly(in);
                        IOUtils.closeQuietly(processOutputStream);
                    }
                }
            }.start();
        }
    }

    /**
     * @see InputStream#read()
     */
    public int read() throws IOException {
        return processInputStream.read();
    }

    /**
     * @see InputStream#read(byte[])
     */
    public int read(byte[] b) throws IOException {
        return processInputStream.read(b);
    }

    /**
     * @see InputStream#read(byte[], int, int)
     */
    public int read(byte[] b, int off, int len) throws IOException {
        return processInputStream.read(b, off, len);
    }

    /**
     * @see InputStream#close()
     */
    public void close() throws IOException {
        IOUtils.closeQuietly(processInputStream);
        IOUtils.closeQuietly(processOutputStream);

        if (process != null) {
            process.destroy();
        }

        if (tmpFile != null) {
            if (!tmpFile.delete()) {
                LOG.warn("Failed to delete tmp file: " + tmpFile);
            }
        }
    }
}