Java tutorial
/** * Copyright 2015 Donald Oakes * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.oakesville.mythling.util; import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import com.oakesville.mythling.BuildConfig; import com.oakesville.mythling.app.AppSettings; import com.oakesville.mythling.app.Localizer; import com.oakesville.mythling.media.Category; import com.oakesville.mythling.media.ChannelGroup; import com.oakesville.mythling.media.Item; import com.oakesville.mythling.media.LiveStreamInfo; import com.oakesville.mythling.media.MediaList; import com.oakesville.mythling.media.MediaSettings; import com.oakesville.mythling.media.MediaSettings.MediaType; import com.oakesville.mythling.media.MediaSettings.MediaTypeDeterminer; import com.oakesville.mythling.media.MediaSettings.SortType; import com.oakesville.mythling.media.MediaSettings.ViewType; import com.oakesville.mythling.media.Movie; import com.oakesville.mythling.media.Recording; import com.oakesville.mythling.media.Song; import com.oakesville.mythling.media.StorageGroup; import com.oakesville.mythling.media.TvEpisode; import com.oakesville.mythling.media.TvShow; import com.oakesville.mythling.media.Video; import android.util.Log; public class MythTvParser implements MediaListParser { private static final String TAG = MythTvParser.class.getSimpleName(); private AppSettings appSettings; private String json; public MythTvParser(AppSettings appSettings, String json) { this.appSettings = appSettings; this.json = json; } public MediaList parseMediaList(MediaType mediaType) throws JSONException, ParseException { return parseMediaList(mediaType, null, null); } /** * @param mediaType * @param storageGroup media storage group */ public MediaList parseMediaList(MediaType mediaType, Map<String, StorageGroup> storageGroups) throws JSONException, ParseException { return parseMediaList(mediaType, storageGroups, null); } /** * @param mediaType * @param storageGroup media storage group * @param basePath base path to trim from filename (when no storage group) */ public MediaList parseMediaList(MediaType mediaType, Map<String, StorageGroup> storageGroups, String basePath) throws JSONException, ParseException { MediaList mediaList = new MediaList(); mediaList.setMediaType(mediaType); mediaList.setBasePath(basePath); long startTime = System.currentTimeMillis(); JSONObject list = new JSONObject(json); SortType sortType = appSettings.getMediaSettings().getSortType(); if (list.has("VideoMetadataInfoList")) { // videos, movies, or tvSeries MediaSettings mediaSettings = appSettings.getMediaSettings(); JSONObject infoList = list.getJSONObject("VideoMetadataInfoList"); if (infoList.has("Version")) mediaList.setMythTvVersion(infoList.getString("Version")); mediaList.setRetrieveDate(parseMythDateTime(infoList.getString("AsOf"))); JSONArray vids = infoList.getJSONArray("VideoMetadataInfos"); String[] movieDirs = appSettings.getMovieDirs(); String[] tvSeriesDirs = appSettings.getTvSeriesDirs(); String[] vidExcludeDirs = appSettings.getVidExcludeDirs(); int count = 0; for (int i = 0; i < vids.length(); i++) { JSONObject vid = (JSONObject) vids.get(i); MediaType type = MediaType.videos; // determine type if (mediaSettings.getTypeDeterminer() == MediaTypeDeterminer.directories) { if (vid.has("FileName")) { String filePath = vid.getString("FileName"); if (storageGroups.get(appSettings.getVideoStorageGroup()) == null && filePath.startsWith(basePath + "/")) filePath = filePath.substring(basePath.length() + 1); for (String movieDir : movieDirs) { if (filePath.startsWith(movieDir)) { type = MediaType.movies; break; } } for (String tvDir : tvSeriesDirs) { if (filePath.startsWith(tvDir)) { type = MediaType.tvSeries; break; } } for (String vidExcludeDir : vidExcludeDirs) { if (filePath.startsWith(vidExcludeDir)) { type = null; break; } } } } else if (mediaSettings.getTypeDeterminer() == MediaTypeDeterminer.metadata) { if (vid.has("Season")) { String season = vid.getString("Season"); if (!season.isEmpty() && !season.equals("0")) type = MediaType.tvSeries; } if (type != MediaType.tvSeries && vid.has("Inetref")) { String inetref = vid.getString("Inetref"); if (!inetref.isEmpty() && !inetref.equals("00000000")) type = MediaType.movies; } } if (type == mediaType) { try { mediaList.addItemUnderPathCategory(buildVideoItem(type, vid, storageGroups)); count++; } catch (NumberFormatException ex) { Log.e(TAG, "NumberFormatException for " + type + " at index: " + count); } } } mediaList.setCount(count); } else if (list.has("ProgramList")) { // recordings JSONObject infoList = list.getJSONObject("ProgramList"); if (infoList.has("Version")) mediaList.setMythTvVersion(infoList.getString("Version")); mediaList.setRetrieveDate(parseMythDateTime(infoList.getString("AsOf"))); mediaList.setCount(infoList.getString("Count")); JSONArray recs = infoList.getJSONArray("Programs"); for (int i = 0; i < recs.length(); i++) { JSONObject rec = (JSONObject) recs.get(i); try { Recording recItem = buildRecordingItem(rec, storageGroups); if (!"Deleted".equals(recItem.getRecordingGroup()) && !"LiveTV".equals(recItem.getRecordingGroup())) { ViewType viewType = appSettings.getMediaSettings().getViewType(); if ((viewType == ViewType.list || viewType == ViewType.split) && (sortType == null || sortType == SortType.byTitle)) { // categorize by title Category cat = mediaList.getCategory(recItem.getTitle()); if (cat == null) { cat = new Category(recItem.getTitle(), MediaType.recordings); mediaList.addCategory(cat); } cat.addItem(recItem); } else { mediaList.addItem(recItem); } } else { mediaList.setCount(mediaList.getCount() - 1); // otherwise reported count will be off } } catch (NumberFormatException ex) { Log.e(TAG, "NumberFormatException for recording at index: " + i); } } } else if (list.has("ProgramGuide")) { // live tv JSONObject infoList = list.getJSONObject("ProgramGuide"); if (infoList.has("Version")) mediaList.setMythTvVersion(infoList.getString("Version")); mediaList.setRetrieveDate(parseMythDateTime(infoList.getString("AsOf"))); mediaList.setCount(infoList.getString("Count")); JSONArray chans = infoList.getJSONArray("Channels"); for (int i = 0; i < chans.length(); i++) { JSONObject chanInfo = (JSONObject) chans.get(i); try { Item show = buildLiveTvItem(chanInfo); if (show != null) { mediaList.addItem(show); show.setPath(""); } } catch (NumberFormatException ex) { Log.e(TAG, "NumberFormatException for live TV at index: " + i); } } } else if (mediaType == MediaType.music) { JSONArray strList = list.getJSONArray("StringList"); mediaList.setCount(strList.length()); StorageGroup musicStorageGroup = storageGroups.get(appSettings.getMusicStorageGroup()); Map<String, String> dirToAlbumArt = null; if (appSettings.isMusicArtAlbum()) dirToAlbumArt = new HashMap<String, String>(); for (int i = 0; i < strList.length(); i++) { String filepath = strList.getString(i); int lastSlash = filepath.lastIndexOf('/'); String filename = filepath; String path = ""; if (lastSlash > 0 && filepath.length() > lastSlash + 1) { filename = filepath.substring(lastSlash + 1); path = filepath.substring(0, lastSlash); } String title = filename; String format = null; int lastDot = filename.lastIndexOf('.'); if (lastDot > 0 && filename.length() > lastDot + 1) { title = filename.substring(0, lastDot); format = filename.substring(lastDot + 1); } if ("jpg".equals(format) && "cover".equals(title)) { if (dirToAlbumArt != null) dirToAlbumArt.put(path, title + ".jpg"); } else if ("jpg".equals(format) || "jpeg".equals(format) // TODO: better check for image type || "png".equals(format) || "gif".equals(format)) { if (dirToAlbumArt != null) { String imgPath = dirToAlbumArt.get(title + '.' + format); if (!"cover.jpg".equals(imgPath)) // prefer cover.jpg dirToAlbumArt.put(path, title + '.' + format); } } else { Song song = new Song(String.valueOf(i), title); song.setPath(path); song.setFileBase(path + "/" + title); song.setFormat(format); song.setStorageGroup(musicStorageGroup); mediaList.addItemUnderPathCategory(song); } } if (dirToAlbumArt != null && !dirToAlbumArt.isEmpty()) { // set album art for (Item item : mediaList.getAllItems()) { String art = dirToAlbumArt.get(item.getPath()); if (art != null) ((Song) item).setAlbumArt(art); } } } if (sortType != null) { startTime = System.currentTimeMillis(); mediaList.sort(sortType, true); if (BuildConfig.DEBUG) Log.d(TAG, " -> media list parse/sort time: " + (System.currentTimeMillis() - startTime) + " ms"); } else { if (BuildConfig.DEBUG) Log.d(TAG, " -> media list parse time: " + (System.currentTimeMillis() - startTime) + " ms"); } return mediaList; } private Video buildVideoItem(MediaType type, JSONObject vid, Map<String, StorageGroup> storageGroups) throws JSONException, ParseException { Video item; if (type == MediaType.movies) { item = new Movie(vid.getString("Id"), vid.getString("Title")); } else if (type == MediaType.tvSeries) { item = new TvEpisode(vid.getString("Id"), vid.getString("Title")); if (vid.has("Season")) { String season = vid.getString("Season"); if (!season.isEmpty() && !season.equals("0")) ((TvEpisode) item).setSeason(Integer.parseInt(season)); } if (vid.has("Episode")) { String episode = vid.getString("Episode"); if (!episode.isEmpty() && !episode.equals("0")) ((TvEpisode) item).setEpisode(Integer.parseInt(episode)); } } else { item = new Video(vid.getString("Id"), vid.getString("Title")); } String filename = vid.getString("FileName"); int lastdot = filename.lastIndexOf('.'); item.setFileBase(filename.substring(0, lastdot)); item.setFormat(filename.substring(lastdot + 1)); if (storageGroups != null) item.setStorageGroup(storageGroups.get(appSettings.getVideoStorageGroup())); if (vid.has("SubTitle")) { String subtitle = vid.getString("SubTitle"); if (!subtitle.isEmpty()) item.setSubTitle(subtitle); } if (vid.has("Director")) { String director = vid.getString("Director"); if (!director.isEmpty() && !director.equals("Unknown")) item.setDirector(director); } if (vid.has("Description")) { String description = vid.getString("Description"); if (!description.equals("None")) item.setSummary(description); } if (vid.has("Inetref")) { String inetref = vid.getString("Inetref"); if (!inetref.isEmpty() && !inetref.equals("00000000")) item.setInternetRef(inetref); } if (vid.has("HomePage")) { String pageUrl = vid.getString("HomePage"); if (!pageUrl.isEmpty()) item.setPageUrl(pageUrl); } if (vid.has("ReleaseDate")) { String releaseDate = vid.getString("ReleaseDate"); if (!releaseDate.isEmpty()) { String dateStr = releaseDate.replace('T', ' '); if (dateStr.endsWith("Z")) dateStr = dateStr.substring(0, dateStr.length() - 1); Date date = Localizer.SERVICE_DATE_FORMAT.parse(dateStr + " UTC"); Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); cal.setTime(date); item.setYear(cal.get(Calendar.YEAR)); } } if (vid.has("UserRating")) { String rating = vid.getString("UserRating"); if (!rating.isEmpty() && !rating.equals("0")) item.setRating(Float.parseFloat(rating) / 2); } if (vid.has("Length")) { item.setLength(Integer.parseInt(vid.getString("Length")) * 60); } if (vid.has("Artwork")) { String artSgName = appSettings.getArtworkStorageGroup(type); StorageGroup artworkStorageGroup = storageGroups == null || AppSettings.ARTWORK_NONE.equals(artSgName) ? null : storageGroups.get(artSgName); if (artworkStorageGroup != null) { JSONObject artwork = vid.getJSONObject("Artwork"); if (artwork.has("ArtworkInfos")) { JSONArray artworkInfos = artwork.getJSONArray("ArtworkInfos"); for (int i = 0; i < artworkInfos.length(); i++) { JSONObject artworkInfo = (JSONObject) artworkInfos.get(i); if (artworkInfo.has("StorageGroup") && artworkStorageGroup.getName().equals(artworkInfo.getString("StorageGroup"))) { if (artworkInfo.has("URL")) { String url = artworkInfo.getString("URL"); // assumes FileName is last parameter int amp = url.lastIndexOf("&FileName="); if (amp > 0) { String artPath = url.substring(amp + 10); if (artPath.startsWith("/")) // indicates no video storage group { for (String artDir : artworkStorageGroup.getDirectories()) { if (artPath.startsWith(artDir)) { artPath = artPath.substring(artDir.length() + 1); break; } } } item.setArtwork(artPath); } } } } } } } if (vid.has("transcoded")) item.setTranscoded("true".equalsIgnoreCase(vid.getString("transcoded"))); return item; } private Recording buildRecordingItem(JSONObject rec, Map<String, StorageGroup> storageGroups) throws JSONException, ParseException { JSONObject channel = rec.getJSONObject("Channel"); String chanId = channel.getString("ChanId"); JSONObject recObj = rec.getJSONObject("Recording"); String startTime = recObj.getString("StartTs").replace('T', ' '); String id = chanId + "~" + startTime; if (id.endsWith("Z")) id = id.substring(0, id.length() - 1); Recording recording = new Recording(id, rec.getString("Title")); recording.setStartTime(parseMythDateTime(startTime)); if (recObj.has("RecordId")) recording.setRecordRuleId(Integer.parseInt(recObj.getString("RecordId"))); if (recObj.has("StorageGroup") && storageGroups != null) recording.setStorageGroup(storageGroups.get(recObj.getString("StorageGroup"))); if (recObj.has("RecGroup")) recording.setRecordingGroup(recObj.getString("RecGroup")); if (recObj.has("Status")) { String status = recObj.getString("Status"); recording.setRecorded(status.equals("-3") || status.equals("Recorded")); } String filename = rec.getString("FileName"); int lastdot = filename.lastIndexOf('.'); recording.setFileBase(filename.substring(0, lastdot)); recording.setFormat(filename.substring(lastdot + 1)); if (channel.has("ChanNum")) recording.setChannelNumber(channel.getString("ChanNum")); if (channel.has("CallSign")) recording.setCallsign(channel.getString("CallSign")); addProgramInfo(recording, rec); if (rec.has("transcoded")) // from stored JSON recording.setTranscoded("true".equalsIgnoreCase(rec.getString("transcoded"))); return recording; } private TvShow buildLiveTvItem(JSONObject chanInfo) throws JSONException, ParseException { String chanId = chanInfo.getString("ChanId"); if (!chanInfo.has("Programs")) return null; JSONArray progs = chanInfo.getJSONArray("Programs"); if (progs.length() == 0) return null; JSONObject prog = (JSONObject) progs.get(0); String startTime = prog.getString("StartTime").replace('T', ' '); if (startTime.endsWith("Z")) startTime = startTime.substring(0, startTime.length() - 1); String id = chanId + "~" + startTime; TvShow tvShow = new TvShow(id, prog.getString("Title")); tvShow.setStartTime(parseMythDateTime(startTime)); tvShow.setProgramStart(startTime); if (chanInfo.has("ChanNum")) tvShow.setChannelNumber(chanInfo.getString("ChanNum")); if (chanInfo.has("CallSign")) tvShow.setCallsign(chanInfo.getString("CallSign")); addProgramInfo(tvShow, prog); if (prog.has("transcoded")) tvShow.setTranscoded("true".equalsIgnoreCase(prog.getString("transcoded"))); return tvShow; } private void addProgramInfo(TvShow tvShow, JSONObject jsonObj) throws JSONException, ParseException { if (jsonObj.has("SubTitle")) { String subtit = jsonObj.getString("SubTitle"); if (!subtit.isEmpty()) tvShow.setSubTitle(subtit); } if (jsonObj.has("Description")) { String description = jsonObj.getString("Description"); if (!description.isEmpty()) tvShow.setDescription(description); } if (jsonObj.has("Airdate")) { String airdate = jsonObj.getString("Airdate"); if (!airdate.isEmpty()) tvShow.setOriginallyAired(Localizer.SERVICE_DATE_FORMAT.parse(airdate)); } if (jsonObj.has("StartTime")) { String startTime = jsonObj.getString("StartTime"); if (!startTime.isEmpty()) { if (startTime.endsWith("Z")) startTime = startTime.substring(0, startTime.length() - 1); tvShow.setProgramStart(startTime.replace('T', ' ')); } } if (jsonObj.has("EndTime")) { String endtime = jsonObj.getString("EndTime"); if (!endtime.isEmpty()) tvShow.setEndTime(parseMythDateTime(endtime)); } if (jsonObj.has("Stars")) { // mythconverg stores program.stars and recorded.stars as a fraction of one Float rating = Float.parseFloat(jsonObj.getString("Stars")) * 10; rating = (float) Math.round(rating) / 2; tvShow.setRating(rating); } if (tvShow instanceof Recording) { if (jsonObj.has("Inetref")) { String inetref = jsonObj.getString("Inetref"); if (!inetref.isEmpty() && !inetref.equals("00000000")) ((Recording) tvShow).setInternetRef(inetref); } if (jsonObj.has("Season")) { String season = jsonObj.getString("Season"); if (!season.isEmpty() && !season.equals("0")) ((Recording) tvShow).setSeason(Integer.parseInt(season)); } if (jsonObj.has("Episode")) { String episode = jsonObj.getString("Episode"); if (!episode.isEmpty() && !episode.equals("0")) ((Recording) tvShow).setEpisode(Integer.parseInt(episode)); } } } public List<LiveStreamInfo> parseStreamInfoList() { List<LiveStreamInfo> streamList = new ArrayList<LiveStreamInfo>(); try { long startTime = System.currentTimeMillis(); JSONObject list = new JSONObject(json).getJSONObject("LiveStreamInfoList"); if (list.has("LiveStreamInfos")) { JSONArray infos = list.getJSONArray("LiveStreamInfos"); for (int i = 0; i < infos.length(); i++) { JSONObject info = (JSONObject) infos.get(i); streamList.add(buildLiveStream(info)); } } if (BuildConfig.DEBUG) Log.d(TAG, " -> live stream info parse time: " + (System.currentTimeMillis() - startTime) + " ms"); } catch (Exception ex) { Log.e(TAG, ex.getMessage(), ex); if (appSettings.isErrorReportingEnabled()) new Reporter(ex).send(); } return streamList; } public LiveStreamInfo parseStreamInfo() { try { long startTime = System.currentTimeMillis(); LiveStreamInfo info = buildLiveStream(new JSONObject(json).getJSONObject("LiveStreamInfo")); if (BuildConfig.DEBUG) Log.d(TAG, " -> live stream info parse time: " + (System.currentTimeMillis() - startTime) + " ms"); return info; } catch (Exception ex) { Log.e(TAG, ex.getMessage(), ex); if (appSettings.isErrorReportingEnabled()) new Reporter(ex).send(); return new LiveStreamInfo(); } } private LiveStreamInfo buildLiveStream(JSONObject obj) throws JSONException { LiveStreamInfo streamInfo = new LiveStreamInfo(); if (obj.has("Id")) streamInfo.setId(obj.getLong("Id")); if (obj.has("StatusInt")) streamInfo.setStatusCode(obj.getInt("StatusInt")); if (obj.has("StatusStr")) streamInfo.setStatus(obj.getString("StatusStr")); if (obj.has("StatusMessage")) streamInfo.setMessage(obj.getString("StatusMessage")); if (obj.has("PercentComplete")) streamInfo.setPercentComplete(obj.getInt("PercentComplete")); if (obj.has("RelativeURL")) streamInfo.setRelativeUrl(obj.getString("RelativeURL").replaceAll(" ", "%20")); if (obj.has("SourceFile")) streamInfo.setFile(obj.getString("SourceFile")); if (obj.has("Width")) streamInfo.setWidth(obj.getInt("Width")); if (obj.has("Height")) streamInfo.setHeight(obj.getInt("Height")); if (obj.has("Bitrate")) streamInfo.setVideoBitrate(obj.getInt("Bitrate")); if (obj.has("AudioBitrate")) streamInfo.setAudioBitrate(obj.getInt("AudioBitrate")); return streamInfo; } public Map<String, StorageGroup> parseStorageGroups() throws JSONException { Map<String, StorageGroup> storageGroups = new HashMap<String, StorageGroup>(); JSONObject dirList = new JSONObject(json).getJSONObject("StorageGroupDirList"); if (dirList.has("StorageGroupDirs")) { JSONArray dirs = dirList.getJSONArray("StorageGroupDirs"); for (int i = 0; i < dirs.length(); i++) { JSONObject dir = (JSONObject) dirs.get(i); String name = dir.getString("GroupName"); StorageGroup storageGroup = storageGroups.get(name); if (storageGroup == null) { storageGroup = new StorageGroup(name); storageGroups.put(name, storageGroup); } String dirPath = dir.getString("DirName"); if (dirPath.endsWith("/")) dirPath = dirPath.substring(0, dirPath.length() - 1); storageGroup.addDirectory(dirPath); if (dir.has("HostName")) storageGroup.setHost(dir.getString("HostName")); } } return storageGroups; } public Map<String, ChannelGroup> parseChannelGroups() throws JSONException { Map<String, ChannelGroup> channelGroups = new HashMap<String, ChannelGroup>(); JSONObject jsonObj = new JSONObject(json); if (jsonObj.has("ChannelGroupList")) { // will not for 0.27 JSONObject groupList = jsonObj.getJSONObject("ChannelGroupList"); if (groupList.has("ChannelGroups")) { JSONArray groups = groupList.getJSONArray("ChannelGroups"); for (int i = 0; i < groups.length(); i++) { JSONObject group = (JSONObject) groups.get(i); String name = group.getString("Name"); String id = group.getString("GroupId"); ChannelGroup channelGroup = new ChannelGroup(id, name); channelGroups.put(name, channelGroup); } } } return channelGroups; } public String parseMythTvSetting(String key) throws JSONException { JSONObject settingsList = new JSONObject(json).getJSONObject("SettingList"); JSONObject settings = settingsList.getJSONObject("Settings"); if (settings.has(key)) return settings.getString(key); else return null; } public Date parseMythDateTime(String dt) throws ParseException { String str = dt.replace('T', ' '); if (str.endsWith("Z")) str = str.substring(0, str.length() - 1); return Localizer.SERVICE_DATE_TIME_RAW_FORMAT.parse(str + " UTC"); } public String parseFrontendStatus() throws JSONException { JSONObject frontendStatus = new JSONObject(json).getJSONObject("FrontendStatus"); JSONObject stateObj = frontendStatus.getJSONObject("State"); return stateObj.getString("state"); } public String parseString() throws JSONException { return new JSONObject(json).getString("String"); } public int parseInt() throws JSONException { return Integer.parseInt(new JSONObject(json).getString("int")); } public int parseUint() throws JSONException { return Integer.parseInt(new JSONObject(json).getString("uint")); } public boolean parseBool() throws JSONException { return Boolean.parseBoolean(new JSONObject(json).getString("bool")); } }