/*
This file is part of JFLICKS.
JFLICKS 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.
JFLICKS 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 JFLICKS. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jflicks.tv.recorder.v4l2;
import java.io.File;
import java.util.ArrayList;
import java.util.Properties;
import org.jflicks.configure.BaseConfiguration;
import org.jflicks.configure.Configuration;
import org.jflicks.configure.NameValue;
import org.jflicks.job.JobContainer;
import org.jflicks.job.JobManager;
import org.jflicks.nms.NMSConstants;
import org.jflicks.tv.Channel;
import org.jflicks.tv.recorder.BaseRecorder;
import org.jflicks.util.LogUtil;
import org.jflicks.util.Util;
/**
* Class that can record from an V4l2.
*
* @author Doug Barnum
* @version 1.0
*/
public class V4l2Recorder extends BaseRecorder {
private JobContainer jobContainer;
private String cardType;
/**
* Simple default constructor.
*/
public V4l2Recorder() {
setTitle("V4l2");
setExtension("mpg");
setQuickTunable(false);
}
/**
* This is the "card type" property from a probe by the v4l2-ctl program.
* The value will be the same for every instance of the same device on
* a computer. For example if there are two PVR-150 PCI cards installed,
* both will have this card type.
*
* @return The card type String.
*/
public String getCardType() {
return (cardType);
}
/**
* This is the "card type" property from a probe by the v4l2-ctl program.
* The value will be the same for every instance of the same device on
* a computer. For example if there are two PVR-150 PCI cards installed,
* both will have this card type.
*
* @param s The card type String.
*/
public void setCardType(String s) {
cardType = s;
}
/**
* {@inheritDoc}
*/
public void startRecording(Channel c, long duration, File destination,
boolean live) {
if (!isRecording()) {
setStartedAt(System.currentTimeMillis());
setChannel(c);
setDuration(duration);
setDestination(destination);
setRecording(true);
setRecordingLiveTV(live);
if (isHlsMode()) {
V4l2RecorderHlsJob job = new V4l2RecorderHlsJob(this);
JobContainer jc = JobManager.getJobContainer(job);
setJobContainer(jc);
jc.start();
} else {
V4l2RecorderJob job = new V4l2RecorderJob(this);
JobContainer jc = JobManager.getJobContainer(job);
setJobContainer(jc);
jc.start();
}
}
}
/**
* {@inheritDoc}
*/
public void stopRecording() {
JobContainer jc = getJobContainer();
if (jc != null) {
jc.stop();
setJobContainer(null);
setRecording(false);
setRecordingLiveTV(false);
}
}
/**
* {@inheritDoc}
*/
public void startStreaming(Channel c, String host, int port) {
if (!isRecording()) {
LogUtil.log(LogUtil.DEBUG, "stream to <" + host + "> port <" + port + ">");
LogUtil.log(LogUtil.DEBUG, "\t channel " + c);
setChannel(c);
setHost(host);
setPort(port);
setRecording(true);
setRecordingLiveTV(true);
V4l2StreamJob job = new V4l2StreamJob(this);
JobContainer jc = JobManager.getJobContainer(job);
setJobContainer(jc);
jc.start();
}
}
/**
* {@inheritDoc}
*/
public void stopStreaming() {
JobContainer jc = getJobContainer();
if (jc != null) {
LogUtil.log(LogUtil.DEBUG, "stopStreaming!");
jc.stop();
setJobContainer(null);
setRecording(false);
setRecordingLiveTV(false);
}
}
/**
* {@inheritDoc}
*/
public void quickTune(Channel c) {
}
/**
* {@inheritDoc}
*/
public void performScan(Channel[] array, String type) {
}
/**
* {@inheritDoc}
*/
public boolean supportsScan() {
return (false);
}
private JobContainer getJobContainer() {
return (jobContainer);
}
private void setJobContainer(JobContainer jc) {
jobContainer = jc;
}
/**
* The configuration properties name is generated from the card type
* property. We expect a file named "SOURCE.v4l2.properties". The
* source will be the "CardType" property from probing the device
* with the exception that any spaces in the text will be replaced
* by underscore.
*
* @return The name of the expected properties file.
*/
public String getPropertiesName() {
String result = null;
String ct = getCardType();
if (ct != null) {
ct = ct.replace(" ", "_");
result = "conf/" + ct + ".v4l2.properties";
}
return (result);
}
/**
* We need to update the "Source" property of the Default Configuration
* instance because there may be more than one V4l2 and this will make
* this instance unique. We will use the Device property to help us.
*/
public void updateDefault() {
BaseConfiguration c = (BaseConfiguration) getDefaultConfiguration();
if (c != null) {
c.setSource(c.getSource() + " " + getDevice());
}
// This is quite hackish but the only analog v4l2 device we
// know that creates transport streams is the Hauppauge HD-PVR.
// All others appear to make program streams. So we should overwrite
// the extension property for the HD-PVR.
LogUtil.log(LogUtil.DEBUG, "card type:" + cardType);
if ((cardType != null) && (cardType.indexOf("HD PVR") != -1)) {
LogUtil.log(LogUtil.DEBUG, "setting extension to ts!");
setExtension("ts");
}
}
/**
* Write out the configuration.
*
* @param c A given Configuration to write.
*/
public void write(Configuration c) {
if (c != null) {
Properties p = toProperties(c);
if (p != null) {
Util.writeProperties(new File(getPropertiesName()), p);
}
}
}
/**
* Convenience method to get the audio input index.
*
* @return An int value.
*/
public int getConfiguredAudioInputIndex() {
int result = 0;
Configuration c = getConfiguration();
if (c != null) {
NameValue[] array = c.getNameValues();
if (array != null) {
for (int i = 0; i < array.length; i++) {
String name = array[i].getName();
if ((name != null)
&& (name.equals(NMSConstants.AUDIO_INPUT_NAME))) {
String tmp = array[i].getValue();
if (tmp != null) {
tmp = tmp.trim();
String[] choices = array[i].getChoices();
if (choices != null) {
for (int j = 0; j < choices.length; j++) {
if (tmp.equals(choices[j])) {
result = j;
}
}
}
}
break;
}
}
}
}
return (result);
}
/**
* Convenience method to get the video input index.
*
* @return An int value.
*/
public int getConfiguredVideoInputIndex() {
int result = 0;
Configuration c = getConfiguration();
if (c != null) {
NameValue[] array = c.getNameValues();
if (array != null) {
for (int i = 0; i < array.length; i++) {
String name = array[i].getName();
if ((name != null)
&& (name.equals(NMSConstants.VIDEO_INPUT_NAME))) {
String tmp = array[i].getValue();
if (tmp != null) {
tmp = tmp.trim();
String[] choices = array[i].getChoices();
if (choices != null) {
for (int j = 0; j < choices.length; j++) {
if (tmp.equals(choices[j])) {
result = j;
}
}
}
}
break;
}
}
}
}
return (result);
}
/**
* Convenience method to get the channel changing script name.
*
* @return A String path pointing to a script file.
*/
public String getConfiguredFrequencyTable() {
String result = null;
Configuration c = getConfiguration();
if (c != null) {
NameValue[] array = c.getNameValues();
if (array != null) {
for (int i = 0; i < array.length; i++) {
String name = array[i].getName();
if ((name != null) && (name.equals(
NMSConstants.FREQUENCY_TABLE_NAME))) {
result = array[i].getValue();
if (result != null) {
result = result.trim();
}
if ((result != null) && (result.length() == 0)) {
result = null;
}
break;
}
}
}
}
return (result);
}
/**
* Convenience method to get the channel changing script name.
*
* @return A String path pointing to a script file.
*/
public String getConfiguredChannelChangeScriptName() {
String result = null;
Configuration c = getConfiguration();
if (c != null) {
NameValue[] array = c.getNameValues();
if (array != null) {
for (int i = 0; i < array.length; i++) {
String name = array[i].getName();
if ((name != null) && (name.equals(
NMSConstants.CHANGE_CHANNEL_SCRIPT_NAME))) {
result = array[i].getValue();
if (result != null) {
result = result.trim();
}
if ((result != null) && (result.length() == 0)) {
result = null;
}
break;
}
}
}
}
return (result);
}
private boolean isControl(NameValue nv) {
boolean result = false;
if (nv != null) {
String name = nv.getName();
if ((name != null)
&& (!name.equals(NMSConstants.AUDIO_INPUT_NAME))
&& (!name.equals(NMSConstants.AUDIO_TRANSCODE_OPTIONS))
&& (!name.equals(NMSConstants.READ_MODE))
&& (!name.equals(NMSConstants.HLS_MODE))
&& (!name.equals(NMSConstants.VIDEO_INPUT_NAME))
&& (!name.equals(NMSConstants.FREQUENCY_TABLE_NAME))
&& (!name.equals(NMSConstants.CUSTOM_CHANNEL_LIST))
&& (!name.equals(NMSConstants.CUSTOM_CHANNEL_LIST_TYPE))
&& (!name.equals(NMSConstants.CHANGE_CHANNEL_SCRIPT_NAME))
&& (!name.equals(NMSConstants.RECORDING_INDEXER_NAME))) {
result = true;
}
}
return (result);
}
/**
* The V4l2 analog devices have a dynamic set of controls defined by
* the driver. By dynamic I mean each driver defines a different
* set. Here we can get what they are in our NameValue object.
*
* @return An array of NameValue instances.
*/
public NameValue[] getConfiguredControls() {
NameValue[] result = null;
Configuration c = getConfiguration();
if (c != null) {
NameValue[] array = c.getNameValues();
if (array != null) {
ArrayList<NameValue> l = new ArrayList<NameValue>();
for (int i = 0; i < array.length; i++) {
if (isControl(array[i])) {
l.add(array[i]);
}
}
if (l.size() > 0) {
result = l.toArray(new NameValue[l.size()]);
}
}
}
return (result);
}
/**
* Convenience method to get the read mode for this v4l2 device.
*
* @return A String defining the read mode from NMSConstants.
*/
public String getConfiguredReadMode() {
String result = null;
Configuration c = getConfiguration();
if (c != null) {
NameValue[] array = c.getNameValues();
if (array != null) {
for (int i = 0; i < array.length; i++) {
String name = array[i].getName();
if ((name != null) && (name.equals(
NMSConstants.READ_MODE))) {
result = array[i].getValue();
if (result != null) {
result = result.trim();
}
if ((result != null) && (result.length() == 0)) {
result = null;
}
break;
}
}
}
}
return (result);
}
}