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}