use of net.pms.io.ProcessWrapperImpl in project UniversalMediaServer by UniversalMediaServer.
the class modAwareHashMap method launchTranscode.
@Override
public ProcessWrapper launchTranscode(DLNAResource dlna, DLNAMediaInfo media, OutputParams params) throws IOException {
params.minBufferSize = params.minFileSize;
params.secondread_minsize = 100000;
// Use device-specific pms conf
PmsConfiguration prev = configuration;
configuration = (DeviceConfiguration) params.mediaRenderer;
RendererConfiguration renderer = params.mediaRenderer;
String filename = dlna.getFileName();
setAudioAndSubs(filename, media, params);
// XXX work around an ffmpeg bug: http://ffmpeg.org/trac/ffmpeg/ticket/998
if (filename.startsWith("mms:")) {
filename = "mmsh:" + filename.substring(4);
}
// check if we have modifier for this url
String r = replacements.match(filename);
if (r != null) {
filename = filename.replaceAll(r, replacements.get(r));
LOGGER.debug("modified url: " + filename);
}
FFmpegOptions customOptions = new FFmpegOptions();
// Gather custom options from various sources in ascending priority:
// - automatic options
String match = autoOptions.match(filename);
if (match != null) {
List<String> opts = autoOptions.get(match);
if (opts != null) {
customOptions.addAll(opts);
}
}
// - (http) header options
if (params.header != null && params.header.length > 0) {
String hdr = new String(params.header);
customOptions.addAll(parseOptions(hdr));
}
// - attached options
String attached = (String) dlna.getAttachment(ID);
if (attached != null) {
customOptions.addAll(parseOptions(attached));
}
// - renderer options
String ffmpegOptions = renderer.getCustomFFmpegOptions();
if (StringUtils.isNotEmpty(ffmpegOptions)) {
customOptions.addAll(parseOptions(ffmpegOptions));
}
// Build the command line
List<String> cmdList = new ArrayList<>();
if (!dlna.isURLResolved()) {
URLResult r1 = ExternalFactory.resolveURL(filename);
if (r1 != null) {
if (r1.precoder != null) {
filename = "-";
if (Platform.isWindows()) {
cmdList.add("cmd.exe");
cmdList.add("/C");
}
cmdList.addAll(r1.precoder);
cmdList.add("|");
} else {
if (StringUtils.isNotEmpty(r1.url)) {
filename = r1.url;
}
}
if (r1.args != null && r1.args.size() > 0) {
customOptions.addAll(r1.args);
}
}
}
cmdList.add(executable());
// XXX squashed bug - without this, ffmpeg hangs waiting for a confirmation
// that it can write to a file that already exists i.e. the named pipe
cmdList.add("-y");
cmdList.add("-loglevel");
if (LOGGER.isTraceEnabled()) {
// Set -loglevel in accordance with LOGGER setting
// Could be changed to "verbose" or "debug" if "info" level is not enough
cmdList.add("info");
} else {
cmdList.add("warning");
}
/*
* FFmpeg uses multithreading by default, so provided that the
* user has not disabled FFmpeg multithreading and has not
* chosen to use more or less threads than are available, do not
* specify how many cores to use.
*/
int nThreads = 1;
if (configuration.isFfmpegMultithreading()) {
if (Runtime.getRuntime().availableProcessors() == configuration.getNumberOfCpuCores()) {
nThreads = 0;
} else {
nThreads = configuration.getNumberOfCpuCores();
}
}
// Decoder threads
if (nThreads > 0) {
cmdList.add("-threads");
cmdList.add("" + nThreads);
}
// Add global and input-file custom options, if any
if (!customOptions.isEmpty()) {
customOptions.transferGlobals(cmdList);
customOptions.transferInputFileOptions(cmdList);
}
if (params.timeseek > 0) {
cmdList.add("-ss");
cmdList.add("" + (int) params.timeseek);
}
cmdList.add("-i");
cmdList.add(filename);
cmdList.addAll(getVideoFilterOptions(dlna, media, params));
// Encoder threads
if (nThreads > 0) {
cmdList.add("-threads");
cmdList.add("" + nThreads);
}
// Add the output options (-f, -c:a, -c:v, etc.)
// Now that inputs and filtering are complete, see if we should
// give the renderer the final say on the command
boolean override = false;
if (renderer instanceof RendererConfiguration.OutputOverride) {
override = ((RendererConfiguration.OutputOverride) renderer).getOutputOptions(cmdList, dlna, this, params);
}
if (!override) {
cmdList.addAll(getVideoTranscodeOptions(dlna, media, params));
// Add video bitrate options
cmdList.addAll(getVideoBitrateOptions(dlna, media, params));
// Add audio bitrate options
cmdList.addAll(getAudioBitrateOptions(dlna, media, params));
// Add any remaining custom options
if (!customOptions.isEmpty()) {
customOptions.transferAll(cmdList);
}
}
// Set up the process
// basename of the named pipe:
String fifoName = String.format("ffmpegwebvideo_%d_%d", Thread.currentThread().getId(), System.currentTimeMillis());
// This process wraps the command that creates the named pipe
PipeProcess pipe = new PipeProcess(fifoName);
// delete the named pipe later; harmless if it isn't created
pipe.deleteLater();
ProcessWrapper mkfifo_process = pipe.getPipeProcess();
/**
* It can take a long time for Windows to create a named pipe (and
* mkfifo can be slow if /tmp isn't memory-mapped), so run this in
* the current thread.
*/
mkfifo_process.runInSameThread();
params.input_pipes[0] = pipe;
// Output file
cmdList.add(pipe.getInputPipe());
// Convert the command list to an array
String[] cmdArray = new String[cmdList.size()];
cmdList.toArray(cmdArray);
// Hook to allow plugins to customize this command line
cmdArray = finalizeTranscoderArgs(filename, dlna, media, params, cmdArray);
// Now launch FFmpeg
ProcessWrapperImpl pw = new ProcessWrapperImpl(cmdArray, params);
// Better late than never
parseMediaInfo(filename, dlna, pw);
// Clean up the mkfifo process when the transcode ends
pw.attachProcess(mkfifo_process);
// Give the mkfifo process a little time
try {
Thread.sleep(300);
} catch (InterruptedException e) {
LOGGER.error("Thread interrupted while waiting for named pipe to be created", e);
}
// Launch the transcode command...
pw.runInNewThread();
// ...and wait briefly to allow it to start
try {
Thread.sleep(200);
} catch (InterruptedException e) {
LOGGER.error("Thread interrupted while waiting for transcode to start", e);
}
configuration = prev;
return pw;
}
use of net.pms.io.ProcessWrapperImpl in project UniversalMediaServer by UniversalMediaServer.
the class DCRaw method getThumbnail.
/**
* Extracts or generates a thumbnail for {@code fileName}.
*
* @param params the {@link OutputParams} to use. Can be {@code null}.
* @param fileName the path of the image file to process.
* @param imageInfo the {@link ImageInfo} for the image file.
* @return A byte array containing the thumbnail or {@code null}.
* @throws IOException if an IO error occurs.
*/
@Override
public byte[] getThumbnail(OutputParams params, String fileName, ImageInfo imageInfo) {
boolean trace = LOGGER.isTraceEnabled();
if (trace) {
LOGGER.trace("Extracting thumbnail from \"{}\" with DCRaw", fileName);
}
if (params == null) {
params = new OutputParams(PMS.getConfiguration());
}
// Use device-specific pms conf
PmsConfiguration configuration = PMS.getConfiguration(params);
params.log = false;
// This is a wild guess at a decent buffer size for an embedded thumbnail.
// Every time the buffer has to grow, the whole buffer must be copied in memory.
params.outputByteArrayStreamBufferSize = 150000;
// First try to get the embedded thumbnail
String[] cmdArray = new String[6];
cmdArray[0] = configuration.getDCRawPath();
cmdArray[1] = "-e";
cmdArray[2] = "-c";
cmdArray[3] = "-M";
cmdArray[4] = "-w";
cmdArray[5] = fileName;
ProcessWrapperImpl pw = new ProcessWrapperImpl(cmdArray, true, params, false, true);
pw.runInSameThread();
byte[] bytes = pw.getOutputByteArray().toByteArray();
List<String> results = pw.getResults();
if (bytes.length > 0) {
// DCRaw doesn't seem to apply Exif Orientation to embedded thumbnails, handle it
boolean isJPEG = (bytes[0] & 0xFF) == 0xFF && (bytes[1] & 0xFF) == 0xD8;
ExifOrientation thumbnailOrientation = null;
Dimension jpegResolution = null;
int exifOrientationOffset = -1;
if (isJPEG) {
try {
ByteArrayReader reader = new ByteArrayReader(bytes);
exifOrientationOffset = ImagesUtil.getJPEGExifIFDTagOffset(0x112, reader);
jpegResolution = ImagesUtil.getJPEGResolution(reader);
} catch (IOException e) {
exifOrientationOffset = -1;
LOGGER.debug("Unexpected error while trying to find Exif orientation offset in embedded thumbnail for \"{}\": {}", fileName, e.getMessage());
LOGGER.trace("", e);
}
if (exifOrientationOffset > 0) {
thumbnailOrientation = ExifOrientation.typeOf(bytes[exifOrientationOffset]);
} else {
LOGGER.debug("Couldn't find Exif orientation in the thumbnail extracted from \"{}\"", fileName);
}
}
ExifOrientation imageOrientation = imageInfo instanceof ExifInfo ? ((ExifInfo) imageInfo).getOriginalExifOrientation() : null;
if (imageOrientation != null && imageOrientation != thumbnailOrientation) {
if (thumbnailOrientation != null) {
if (imageInfo.getWidth() > 0 && imageInfo.getHeight() > 0 && jpegResolution != null && jpegResolution.getWidth() > 0 && jpegResolution.getHeight() > 0) {
// Try to determine which orientation to trust
double imageAspect, thumbnailAspect;
if (ImagesUtil.isExifAxesSwapNeeded(imageOrientation)) {
imageAspect = (double) imageInfo.getHeight() / imageInfo.getWidth();
} else {
imageAspect = (double) imageInfo.getWidth() / imageInfo.getHeight();
}
if (ImagesUtil.isExifAxesSwapNeeded(thumbnailOrientation)) {
thumbnailAspect = (double) jpegResolution.getHeight() / jpegResolution.getWidth();
} else {
thumbnailAspect = (double) jpegResolution.getWidth() / jpegResolution.getHeight();
}
if (Math.abs(imageAspect - thumbnailAspect) > 0.001d) {
// The image and the thumbnail seems to have different aspect ratios, use that of the image
bytes[exifOrientationOffset] = (byte) imageOrientation.getValue();
}
}
} else if (imageOrientation != ExifOrientation.TOP_LEFT) {
// Apply the orientation to the thumbnail
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Thumbnails.of(new ByteArrayInputStream(bytes)).scale(1.0d).addFilter(ExifFilterUtils.getFilterForOrientation(imageOrientation.getThumbnailatorOrientation())).outputFormat(// PNG here to avoid further degradation from rotation
"PNG").outputQuality(1.0f).toOutputStream(outputStream);
bytes = outputStream.toByteArray();
} catch (IOException e) {
LOGGER.error("Unexpected error when trying to rotate thumbnail for \"{}\" - cancelling rotation: {}", fileName, e.getMessage());
LOGGER.trace("", e);
}
}
}
}
if (bytes.length == 0 || !results.isEmpty() && results.get(0).contains("has no thumbnail")) {
// No embedded thumbnail retrieved, generate thumbnail from the actual file
if (trace) {
LOGGER.trace("No embedded thumbnail found in \"{}\", " + "trying to generate thumbnail from the image itself", fileName);
}
params.outputByteArrayStreamBufferSize = imageInfo != null && imageInfo.getSize() != ImageInfo.SIZE_UNKNOWN ? (int) imageInfo.getSize() / 4 : 500000;
cmdArray[1] = "-h";
pw = new ProcessWrapperImpl(cmdArray, true, params);
pw.runInSameThread();
bytes = pw.getOutputByteArray().toByteArray();
}
if (trace && (bytes == null || bytes.length == 0)) {
LOGGER.trace("Failed to generate thumbnail with DCRaw for image \"{}\"", fileName);
}
return bytes != null && bytes.length > 0 ? bytes : null;
}
use of net.pms.io.ProcessWrapperImpl in project UniversalMediaServer by UniversalMediaServer.
the class DCRaw method parse.
/**
* Parses {@code file} and stores the result in {@code media}.
*
* @param media the {@link DLNAMediaInfo} instance to store the parse
* results in.
* @param file the {@link File} to parse.
*/
@Override
public void parse(DLNAMediaInfo media, File file) {
if (media == null) {
throw new NullPointerException("media cannot be null");
}
if (file == null) {
throw new NullPointerException("file cannot be null");
}
OutputParams params = new OutputParams(configuration);
params.log = true;
String[] cmdArray = new String[4];
cmdArray[0] = configuration.getDCRawPath();
cmdArray[1] = "-i";
cmdArray[2] = "-v";
cmdArray[3] = file.getAbsolutePath();
ProcessWrapperImpl pw = new ProcessWrapperImpl(cmdArray, params, true, false);
pw.runInSameThread();
List<String> list = pw.getOtherResults();
Pattern pattern = Pattern.compile("^Output size:\\s*(\\d+)\\s*x\\s*(\\d+)");
Matcher matcher;
for (String s : list) {
matcher = pattern.matcher(s);
if (matcher.find()) {
media.setWidth(Integer.parseInt(matcher.group(1)));
media.setHeight(Integer.parseInt(matcher.group(2)));
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Parsed resolution {} x {} for image \"{}\" from DCRaw output", Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)), file.getPath());
}
break;
}
}
}
use of net.pms.io.ProcessWrapperImpl in project UniversalMediaServer by UniversalMediaServer.
the class FFmpegAudio method launchTranscode.
@Override
public synchronized ProcessWrapper launchTranscode(DLNAResource dlna, DLNAMediaInfo media, OutputParams params) throws IOException {
PmsConfiguration prev = configuration;
// Use device-specific pms conf
configuration = (DeviceConfiguration) params.mediaRenderer;
final String filename = dlna.getFileName();
params.maxBufferSize = configuration.getMaxAudioBuffer();
params.waitbeforestart = 2000;
params.manageFastStart();
/*
* FFmpeg uses multithreading by default, so provided that the
* user has not disabled FFmpeg multithreading and has not
* chosen to use more or less threads than are available, do not
* specify how many cores to use.
*/
int nThreads = 1;
if (configuration.isFfmpegMultithreading()) {
if (Runtime.getRuntime().availableProcessors() == configuration.getNumberOfCpuCores()) {
nThreads = 0;
} else {
nThreads = configuration.getNumberOfCpuCores();
}
}
List<String> cmdList = new ArrayList<>();
cmdList.add(executable());
cmdList.add("-loglevel");
if (LOGGER.isTraceEnabled()) {
// Set -loglevel in accordance with LOGGER setting
// Could be changed to "verbose" or "debug" if "info" level is not enough
cmdList.add("info");
} else {
cmdList.add("warning");
}
if (params.timeseek > 0) {
cmdList.add("-ss");
cmdList.add("" + params.timeseek);
}
// Decoder threads
if (nThreads > 0) {
cmdList.add("-threads");
cmdList.add("" + nThreads);
}
cmdList.add("-i");
cmdList.add(filename);
// Make sure FFmpeg doesn't try to encode embedded images into the stream
cmdList.add("-vn");
// Encoder threads
if (nThreads > 0) {
cmdList.add("-threads");
cmdList.add("" + nThreads);
}
if (params.timeend > 0) {
cmdList.add("-t");
cmdList.add("" + params.timeend);
}
if (params.mediaRenderer.isTranscodeToMP3()) {
cmdList.add("-f");
cmdList.add("mp3");
cmdList.add("-ab");
cmdList.add("320000");
} else if (params.mediaRenderer.isTranscodeToWAV()) {
cmdList.add("-f");
cmdList.add("wav");
} else {
// default: LPCM
cmdList.add("-f");
cmdList.add("s16be");
}
if (configuration.isAudioResample()) {
if (params.mediaRenderer.isTranscodeAudioTo441()) {
cmdList.add("-ar");
cmdList.add("44100");
cmdList.add("-ac");
cmdList.add("2");
} else {
cmdList.add("-ar");
cmdList.add("48000");
cmdList.add("-ac");
cmdList.add("2");
}
}
cmdList.add("pipe:");
String[] cmdArray = new String[cmdList.size()];
cmdList.toArray(cmdArray);
cmdArray = finalizeTranscoderArgs(filename, dlna, media, params, cmdArray);
ProcessWrapperImpl pw = new ProcessWrapperImpl(cmdArray, params);
pw.runInNewThread();
configuration = prev;
return pw;
}
use of net.pms.io.ProcessWrapperImpl in project UniversalMediaServer by UniversalMediaServer.
the class DVDISOTitle method resolveOnce.
@Override
protected void resolveOnce() {
if (getMedia() == null) {
setMedia(new DLNAMediaInfo());
}
OutputParams params = new OutputParams(configuration);
params.maxBufferSize = 1;
params.log = true;
boolean generateThumbnails = false;
if (configuration.isDvdIsoThumbnails()) {
try {
params.workDir = configuration.getTempFolder();
generateThumbnails = true;
} catch (IOException e1) {
LOGGER.error("Could not create temporary folder, DVD thumbnails won't be generated: {}", e1.getMessage());
LOGGER.trace("", e1);
}
}
String[] cmd;
if (generateThumbnails) {
String outFolder = "jpeg:outdir=mplayer_thumbs:subdirs=\"" + this.hashCode() + "\"";
cmd = new String[] { configuration.getMplayerPath(), "-identify", "-ss", Integer.toString(configuration.getThumbnailSeekPos()), "-frames", "1", "-v", "-ao", "null", "-vo", outFolder, "-dvd-device", ProcessUtil.getShortFileNameIfWideChars(file.getAbsolutePath()), "dvd://" + title };
} else {
cmd = new String[] { configuration.getMplayerPath(), "-identify", "-endpos", "0", "-v", "-ao", "null", "-vc", "null", "-vo", "null", "-dvd-device", ProcessUtil.getShortFileNameIfWideChars(file.getAbsolutePath()), "dvd://" + title };
}
final ProcessWrapperImpl pw = new ProcessWrapperImpl(cmd, params, true, false);
Runnable r = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
pw.stopProcess();
}
};
Thread failsafe = new Thread(r, "DVD ISO Title Failsafe");
failsafe.start();
pw.runInSameThread();
List<String> lines = pw.getOtherResults();
String duration = null;
int nbsectors = 0;
String fps = null;
String aspect = null;
String width = null;
String height = null;
String codecV = null;
ArrayList<DLNAMediaAudio> audioTracks = new ArrayList<>();
ArrayList<DLNAMediaSubtitle> subtitles = new ArrayList<>();
if (lines != null) {
for (String line : lines) {
if (line.startsWith("DVD start=")) {
nbsectors = Integer.parseInt(line.substring(line.lastIndexOf('=') + 1).trim());
} else if (line.startsWith("audio stream:")) {
DLNAMediaAudio audio = parseMEncoderAudioStream(line);
if (audio != null) {
audioTracks.add(audio);
}
} else if (line.startsWith("subtitle")) {
DLNAMediaSubtitle subtitle = parseMEncoderSubtitleStream(line);
if (subtitle != null) {
subtitles.add(subtitle);
}
} else if (line.startsWith("ID_VIDEO_WIDTH=")) {
width = line.substring(line.indexOf("ID_VIDEO_WIDTH=") + 15).trim();
} else if (line.startsWith("ID_VIDEO_HEIGHT=")) {
height = line.substring(line.indexOf("ID_VIDEO_HEIGHT=") + 16).trim();
} else if (line.startsWith("ID_VIDEO_FPS=")) {
fps = line.substring(line.indexOf("ID_VIDEO_FPS=") + 13).trim();
} else if (line.startsWith("ID_LENGTH=")) {
duration = line.substring(line.indexOf("ID_LENGTH=") + 10).trim();
} else if (line.startsWith("ID_VIDEO_ASPECT=")) {
aspect = line.substring(line.indexOf("ID_VIDEO_ASPECT=") + 16).trim();
} else if (line.startsWith("ID_VIDEO_FORMAT=")) {
String formatStr = line.substring(line.lastIndexOf("=") + 1).trim();
if ("0x31435657".equals(formatStr)) {
codecV = FormatConfiguration.VC1;
} else if ("0x10000001".equals(formatStr)) {
codecV = FormatConfiguration.MPEG1;
} else if ("0x10000002".equals(formatStr)) {
codecV = FormatConfiguration.MPEG2;
} else {
LOGGER.warn("Unknown video format value \"{}\"", formatStr);
}
}
}
}
if (generateThumbnails) {
try {
String frameName = "" + this.hashCode();
frameName = configuration.getTempFolder() + "/mplayer_thumbs/" + frameName + "00000001/00000001.jpg";
frameName = frameName.replace(',', '_');
File jpg = new File(frameName);
if (jpg.exists()) {
try (InputStream inputStream = new FileInputStream(jpg)) {
getMedia().setThumb(DLNAThumbnail.toThumbnail(inputStream, 640, 480, ScaleType.MAX, ImageFormat.SOURCE, false));
}
if (!jpg.delete()) {
jpg.deleteOnExit();
}
// Try and retry
if (!jpg.getParentFile().delete() && !jpg.getParentFile().delete()) {
LOGGER.debug("Failed to delete \"" + jpg.getParentFile().getAbsolutePath() + "\"");
}
}
jpg = new File(frameName + "1.jpg");
if (jpg.exists()) {
if (!jpg.delete()) {
jpg.deleteOnExit();
}
if (!jpg.getParentFile().delete()) {
if (!jpg.getParentFile().delete()) {
jpg.getParentFile().deleteOnExit();
}
}
}
} catch (IOException e) {
LOGGER.error("Error during DVD ISO thumbnail retrieval: {}", e.getMessage());
LOGGER.trace("", e);
}
}
// No point in trying to re-parse the thumbnail later
getMedia().setThumbready(true);
length = nbsectors * 2048;
double d = 0;
if (duration != null) {
d = Double.parseDouble(duration);
}
getMedia().setAudioTracksList(audioTracks);
getMedia().setSubtitleTracksList(subtitles);
if (duration != null) {
getMedia().setDuration(d);
}
getMedia().setFrameRate(fps);
getMedia().setAspectRatioDvdIso(aspect);
getMedia().setDvdtrack(title);
getMedia().setContainer(FormatConfiguration.ISO);
getMedia().setCodecV(codecV != null ? codecV : FormatConfiguration.MPEG2);
getMedia().setVideoTrackCount(1);
try {
getMedia().setWidth(Integer.parseInt(width));
} catch (NumberFormatException nfe) {
LOGGER.debug("Could not parse DVD video width \"{}\"", width);
}
try {
getMedia().setHeight(Integer.parseInt(height));
} catch (NumberFormatException nfe) {
LOGGER.debug("Could not parse DVD video height \"{}\"", height);
}
getMedia().setMediaparsed(true);
}
Aggregations