001package silicondust;
002/*-
003 * ##########################################################################
004 * TV H/W, EPGs, and Recording
005 * $Id: HDHRRecording.java 6118 2020-06-04 19:31:45Z ball $
006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/silicondust/trunk/src/main/java/silicondust/HDHRRecording.java $
007 * %%
008 * Copyright (C) 2013 - 2020 Allen D. Ball
009 * %%
010 * Licensed under the Apache License, Version 2.0 (the "License");
011 * you may not use this file except in compliance with the License.
012 * You may obtain a copy of the License at
013 *
014 *      http://www.apache.org/licenses/LICENSE-2.0
015 *
016 * Unless required by applicable law or agreed to in writing, software
017 * distributed under the License is distributed on an "AS IS" BASIS,
018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
019 * See the License for the specific language governing permissions and
020 * limitations under the License.
021 * ##########################################################################
022 */
023import ball.activation.DataSourceDefaultMethods;
024import ball.activation.ReaderWriterDataSource;
025import ball.annotation.MatcherGroup;
026import ball.annotation.PatternRegex;
027import ball.util.PatternMatcherBean;
028import com.fasterxml.jackson.databind.JsonNode;
029import java.io.EOFException;
030import java.io.File;
031import java.io.IOException;
032import java.io.InputStream;
033import java.io.OutputStream;
034import java.nio.ByteBuffer;
035import java.text.SimpleDateFormat;
036import java.util.TimeZone;
037import javax.activation.FileDataSource;
038import lombok.ToString;
039
040import static java.nio.charset.StandardCharsets.UTF_8;
041import static org.apache.commons.lang3.StringUtils.isBlank;
042
043/**
044 * {@link.uri http://www.silicondust.com/ SiliconDust} HDHomeRun recording.
045 *
046 * {@bean.info}
047 *
048 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
049 * @version $Revision: 6118 $
050 */
051@PatternRegex("(?is)^(?<title>.+)( (?<episodeNumber>S[0-9]+E[0-9]+))( (?<origAirDate>[0-9]{8}))( \\[(?<startTime>[0-9]{8}-[0-9]{4})\\])[.]mpg$")
052public class HDHRRecording extends FileDataSource
053                           implements DataSourceDefaultMethods,
054                                      PatternMatcherBean {
055    private static final SimpleDateFormat YYYYMMDD =
056        new SimpleDateFormatImpl("yyyyMMdd");
057    private static final SimpleDateFormat YYYYMMDD_HHMM =
058        new SimpleDateFormatImpl("yyyyMMdd-HHmm");
059
060    private final long offset;
061    private final JsonNode json;
062    private String title = null;
063    private String episodeTitle = null;
064    private String episodeNumber = null;
065    private Long origAirDate = null;
066    private Long startTime = null;
067
068    /**
069     * Sole constructor.
070     *
071     * @param   file            The recording {@link java.io.File}.
072     *
073     * @throws  IOException     If the recording metadata cannot be read.
074     */
075    public HDHRRecording(File file) throws IOException {
076        super(file);
077
078        PatternMatcherBean.super.initialize(getName());
079
080        long offset = 0;
081        ReaderWriterDataSource json =
082            new ReaderWriterDataSource(null, null, UTF_8);
083        ByteBuffer buffer = ByteBuffer.allocate(188);
084
085        try (InputStream in = super.getInputStream();
086             OutputStream out = json.getOutputStream()) {
087            for (;;) {
088                try {
089                    buffer.clear();
090                    buffer.limit(in.read(buffer.array()));
091
092                    if (buffer.limit() == buffer.capacity()) {
093                        if (buffer.get(0) == (byte) 'G'
094                            && (buffer.get(1) == (byte) '_'
095                                || buffer.get(1) == (byte) 0037)
096                            && buffer.get(2) == (byte) 0372) {
097                            offset += buffer.limit();
098
099                            buffer.position(buffer.position() + 4);
100
101                            out.write(buffer.array(),
102                                      buffer.position(), buffer.remaining());
103                        } else {
104                            break;
105                        }
106                    } else {
107                        break;
108                    }
109                } catch (IllegalArgumentException exception) {
110                    throw new EOFException("Expected "
111                                           + buffer.capacity() + " bytes");
112                }
113            }
114        }
115
116        this.offset = offset;
117        this.json = ObjectMapperConfiguration.MAPPER.readTree(json.toString());
118    }
119
120    @MatcherGroup(1)
121    protected void setTitle(String string) {
122        if (! isBlank(string)) {
123            title = string;
124        }
125    }
126
127    protected void setEpisodeTitle(String string) {
128        if (! isBlank(string)) {
129            episodeTitle = string;
130        }
131    }
132
133    @MatcherGroup(3)
134    protected void setEpisodeNumber(String string) {
135        if (! isBlank(string)) {
136            episodeNumber = string;
137        }
138    }
139
140    @MatcherGroup(5)
141    protected void setOrigAirDate(String string) {
142        try {
143            if (! isBlank(string)) {
144                origAirDate = YYYYMMDD.parse(string).getTime();
145            }
146        } catch (RuntimeException exception) {
147            throw exception;
148        } catch (Exception exception) {
149            throw new IllegalArgumentException(String.valueOf(string),
150                                               exception);
151        }
152    }
153
154    @MatcherGroup(7)
155    protected void setStartTime(String string) {
156        try {
157            if (! isBlank(string)) {
158                startTime = YYYYMMDD_HHMM.parse(string).getTime();
159            }
160        } catch (RuntimeException exception) {
161            throw exception;
162        } catch (Exception exception) {
163            throw new IllegalArgumentException(String.valueOf(string),
164                                               exception);
165        }
166    }
167
168    public long getOffset() { return offset; }
169
170    @Override
171    public InputStream getInputStream() throws IOException {
172        InputStream in = super.getInputStream();
173
174        in.skip(getOffset());
175
176        return in;
177    }
178
179    @Override
180    public OutputStream getOutputStream() throws IOException {
181        return DataSourceDefaultMethods.super.getOutputStream();
182    }
183
184    @Override
185    public String toString() { return String.valueOf(json); }
186
187    @ToString
188    private static class SimpleDateFormatImpl extends SimpleDateFormat {
189        private static final long serialVersionUID = -8532683766075038547L;
190
191        private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
192
193        public SimpleDateFormatImpl(String pattern) {
194            super(pattern);
195
196            setLenient(true);
197            setTimeZone(UTC);
198        }
199    }
200}