private void createPlaylist() {
    String showName = MediaFileAPI.GetMediaTitle(mediaFile);
    int numberOfMediaFileSegments = MediaFileAPI.GetNumberOfSegments(mediaFile);
    Log.debug("Number of media file segments: " + numberOfMediaFileSegments);

    for (int i = 0; i < numberOfMediaFileSegments; i++) {
      // get length of current media file segment
      long mediaFileSegmentDurationInMillis = MediaFileAPI.GetDurationForSegment(mediaFile, i);
      long mediaFileSegmentDurationInSeconds =
          mediaFileSegmentDurationInMillis / 1000; // milliseconds to seconds
      Log.debug(
          "mediaFileSegmentDurationInMillis (" + i + "): " + mediaFileSegmentDurationInMillis);
      Log.debug(
          "mediaFileSegmentDurationInSeconds (" + i + "): " + mediaFileSegmentDurationInSeconds);

      int sequence = 0;
      for (int j = 0; j < mediaFileSegmentDurationInSeconds; j += SegmentPlaylist.TARGET_DURATION) {
        long currentDuration =
            Math.min(SegmentPlaylist.TARGET_DURATION, mediaFileSegmentDurationInSeconds - j);

        Segment newSegment = new Segment(currentDuration, sequence, i, showName);

        segmentList.add(newSegment);

        sequence++;
      }
    }
    if (segmentList != null) {
      Log.debug("segmentList size: " + segmentList.size());
    }
  }
  public String toString() {
    StringBuilder sb = new StringBuilder();

    String showName = MediaFileAPI.GetMediaTitle(mediaFile);
    int numberOfMediaFileSegments = MediaFileAPI.GetNumberOfSegments(mediaFile);
    int segmentCount = (isFileCurrentlyRecording) ? segmentList.size() - 1 : segmentList.size();
    Log.debug("Show: " + showName);
    Log.debug("MediaFileId: " + mediaFileId);
    Log.debug("Number Of MediaFile segments: " + numberOfMediaFileSegments);
    Log.debug("Is file currently recording: " + isFileCurrentlyRecording);
    Log.debug("Number of playlist segments: " + segmentCount);

    sb.append("#EXTM3U" + LINE_TERM);
    Log.debug("#EXTM3U");
    sb.append("#EXT-X-TARGETDURATION:" + TARGET_DURATION + LINE_TERM);
    Log.debug("#EXT-X-TARGETDURATION:" + SegmentPlaylist.TARGET_DURATION);
    //        sb.append("#EXT-X-MEDIA-SEQUENCE:1" + LINE_TERM);
    //        Log.debug("#EXT-X-MEDIA-SEQUENCE:1");

    for (int i = 0; i < segmentCount; i++) {
      Segment currentSegment = segmentList.get(i);
      Segment previousSegment = (i == 0) ? null : segmentList.get(i - 1);
      Segment nextSegment = (i == segmentCount - 1) ? null : segmentList.get(i + 1);
      String str = currentSegment.toString();
      sb.append(str);

      // first or last playlist segment for each media file segment
      // printing the whole playlist makes the log harder to read
      if ((previousSegment == null)
          || (nextSegment == null)
          || (previousSegment.mediaFileSegment != currentSegment.mediaFileSegment)
          || (currentSegment.mediaFileSegment != nextSegment.mediaFileSegment)) {
        Log.debug(str);
      }
    }

    //      #EXT-X-MEDIA-SEQUENCE:<number>
    //      #EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ>
    //      #EXT-X-ALLOW-CACHE:<YES|NO>
    //      #EXT-X-STREAM-INF

    if (!isFileCurrentlyRecording) {
      sb.append("#EXT-X-ENDLIST" + LINE_TERM);
      Log.debug("#EXT-X-ENDLIST");
    }
    return sb.toString();
  }
示例#3
0
  public boolean canAccept(IMediaResource res) {
    if (res instanceof IMediaFolder) return true;

    if (res instanceof IMediaFile) {
      return (MediaFileAPI.IsBluRay(res.getMediaObject()));
    } else {
      return false;
    }
  }
示例#4
0
  public static String GetUserRating(Object MediaObject) {
    String Rating = MediaFileAPI.GetMediaFileMetadata(MediaObject, "UserRating");
    String Ratingscrubbed = "";
    if (Rating.contains(".") && Rating.length() != 0) {
      Ratingscrubbed = Rating.substring(0, Rating.indexOf("."));
    } else if (Rating.length() != 0 && !Rating.contains("awaiting")) {
      Ratingscrubbed = Rating;
    } else {
      Ratingscrubbed = "0";
    }

    return Ratingscrubbed;
  }
  public SegmentPlaylist(
      HttpServletRequest req, Object mediaFile, String conversionId, String quality) {
    this.req = req;
    this.mediaFile = mediaFile;
    this.mediaFileId = MediaFileAPI.GetMediaFileID(mediaFile);
    this.segmentList = new ArrayList<Segment>();
    this.conversionId = conversionId;
    this.quality = quality;
    this.isFileCurrentlyRecording = MediaFileAPI.IsFileCurrentlyRecording(mediaFile);

    if (!this.isFileCurrentlyRecording) {
      createPlaylist();
    } else {
      // retry until there's at least one full segment recorded
      while (this.isFileCurrentlyRecording && segmentList.size() <= 1) {
        this.segmentList.clear();

        createPlaylist();

        long mediaFileSegmentDurationInMillis = MediaFileAPI.GetDurationForSegment(mediaFile, 0);
        long mediaFileSegmentDurationInSeconds =
            mediaFileSegmentDurationInMillis / 1000; // milliseconds to seconds

        if (this.isFileCurrentlyRecording
            && (mediaFileSegmentDurationInSeconds < SegmentPlaylist.TARGET_DURATION)) {
          try {
            // wait until we'll have a full first segment to return
            Thread.sleep(
                (SegmentPlaylist.TARGET_DURATION - mediaFileSegmentDurationInSeconds + 1) * 1000);
          } catch (InterruptedException e) {
            Log.warn(e.getMessage());
          }
        }

        this.isFileCurrentlyRecording = MediaFileAPI.IsFileCurrentlyRecording(mediaFile);
      }
    }
  }
  private File getDefaultArtifact(Object mediaObject, MediaArtifactType artifactType) {
    String def = null;
    if (artifactType == MediaArtifactType.POSTER) {
      def =
          MediaFileAPI.GetMediaFileMetadata(
              mediaObject, ISageCustomMetadataRW.FieldName.DEFAULT_POSTER);
    } else if (artifactType == MediaArtifactType.BACKGROUND) {
      def =
          MediaFileAPI.GetMediaFileMetadata(
              mediaObject, ISageCustomMetadataRW.FieldName.DEFAULT_BACKGROUND);
    } else if (artifactType == MediaArtifactType.BANNER) {
      def =
          MediaFileAPI.GetMediaFileMetadata(
              mediaObject, ISageCustomMetadataRW.FieldName.DEFAULT_BANNER);
    }

    if (!StringUtils.isEmpty(def)) {
      File f = new File(GetFanartCentralFolder(), def);
      if (f.exists() && f.isFile()) return f;
    }

    return null;
  }
  public void SetFanartArtifact(
      Object mediaObject,
      File fanart,
      MediaType mediaType,
      String mediaTitle,
      MediaArtifactType artifactType,
      String artifactTitle,
      Map<String, String> metadata) {
    try {
      String central = (new File(GetFanartCentralFolder())).getCanonicalPath();
      String file = fanart.getCanonicalPath();

      if (!file.startsWith(central)) {
        throw new Exception(
            "You can only set a fanart artifact relative to the fanart folder. Folder: "
                + central
                + "; fanart: "
                + file);
      }

      String art = file.substring(central.length());
      if (art.startsWith(File.separator)) {
        art = StringUtils.strip(art, File.separator);
      }

      String key = null;
      if (artifactType == MediaArtifactType.POSTER) {
        key = ISageCustomMetadataRW.FieldName.DEFAULT_POSTER;
      } else if (artifactType == MediaArtifactType.BACKGROUND) {
        key = ISageCustomMetadataRW.FieldName.DEFAULT_BACKGROUND;
      } else if (artifactType == MediaArtifactType.BANNER) {
        key = ISageCustomMetadataRW.FieldName.DEFAULT_BANNER;
      }
      if (key == null)
        throw new Exception(
            "Invalid Artifact Type: " + artifactType + "; Can't set default artifact.");
      MediaFileAPI.SetMediaFileMetadata(mediaObject, key, art);
    } catch (Exception e) {
      log.warn("Failed to set the default fanart artifact!", e);
    }
  }
示例#8
0
  /**
   * Interface definition for implementation classes that listen for events from the SageTV core
   *
   * <p>Variable types are in brackets[] after the var name unless they are the same as the var name
   * itself. List of known core events:
   *
   * <p>MediaFileImported - vars: MediaFile ImportingStarted ImportingCompleted RecordingCompleted
   * (called when a complete recording is done) vars: MediaFile RecordingStarted (called when any
   * kind of recording is started) vars: MediaFile RecordingStopped (called whenever a recording is
   * stopped for any reason) vars: MediaFile AllPluginsLoaded RecordingScheduleChanged
   * ConflictStatusChanged SystemMessagePosted vars: SystemMessage EPGUpdateCompleted
   * MediaFileRemoved vars: MediaFile PlaybackStopped (called when the file is closed) vars:
   * MediaFile, UIContext[String], Duration[Long], MediaTime[Long], ChapterNum[Integer],
   * TitleNum[Integer] PlaybackFinished (called at the EOF) vars: MediaFile, UIContext[String],
   * Duration[Long], MediaTime[Long], ChapterNum[Integer], TitleNum[Integer] PlaybackStarted vars:
   * MediaFile, UIContext[String], Duration[Long], MediaTime[Long], ChapterNum[Integer],
   * TitleNum[Integer] FavoriteAdded vars: Favorite FavoriteModified vars: Favorite FavoriteRemoved
   * vars: Favorite PlaylistAdded vars: Playlist, UIContext[String] PlaylistModified vars: Playlist,
   * UIContext[String] PlaylistRemoved vars: Playlist, UIContext[String] ClientConnected vars:
   * IPAddress[String], MACAddress[String] (if its a placeshifter/extender, MACAddress is null
   * otherwise) ClientDisconnected vars: IPAddress[String], MACAddress[String] (if its a
   * placeshifter/extender, MACAddress is null otherwise)
   *
   * <p>This is a callback method invoked from the SageTV core for any events the listener has
   * subscribed to. See the sage.SageTVPluginRegistry interface definition for details regarding
   * subscribing and unsubscribing to events. The eventName will be a predefined String which
   * indicates the event type. The eventVars will be a Map of variables specific to the event
   * information. This Map should NOT be modified. The keys to the eventVars Map will generally be
   * Strings; but this may change in the future and plugins that submit events are not required to
   * follow that rule.
   */
  @Override
  public synchronized void sageEvent(String eventName, java.util.Map eventVars) {

    Log.getInstance().write(Log.LOGLEVEL_TRACE, "sageEvent: Event received = " + eventName);

    // Check that we have the right event.
    if (!(eventName.startsWith("RecordingCompleted") || eventName.startsWith("RecordingStopped"))) {
      Log.getInstance()
          .write(Log.LOGLEVEL_WARN, "sageEvent: Unexpected event received = " + eventName);
      return;
    }

    // Check that we have a valid MediaFile.
    Object MediaFile = eventVars.get("MediaFile");

    if (MediaFile == null) {
      Log.getInstance().write(Log.LOGLEVEL_WARN, "sageEvent: null MediaFile");
      return;
    }

    Log.getInstance()
        .write(
            Log.LOGLEVEL_TRACE,
            "sageEvent: Finished recording "
                + AiringAPI.GetAiringTitle(MediaFile)
                + " - "
                + ShowAPI.GetShowEpisode(MediaFile));

    // If it's a Manual, Favorite, or TimedRecord (manual) we do not need to worry about it.
    if (AiringAPI.IsFavorite(MediaFile) || AiringAPI.IsManualRecord(MediaFile)) {
      Log.getInstance().write(Log.LOGLEVEL_TRACE, "sageEvent: Is not an Intelligent Recording.");
      return;
    }

    // Create the DataStore which will allow us to access the data for this MediaFile.
    DataStore store = new DataStore(MediaFile);

    int maxToKeep;

    // If it's monitored keep the number specified. If it's not monitored use the
    // global default.
    if (store.isMonitored()) {
      Log.getInstance().write(Log.LOGLEVEL_TRACE, "sageEvent: Using max for this show.");
      maxToKeep = store.getMax();
    } else {
      Log.getInstance().write(Log.LOGLEVEL_TRACE, "sageEvent: Using global max.");
      maxToKeep = Util.GetIntProperty(PROPERTY_DEFAULT_MAX, DEFAULT_MAX_STRING);
    }

    Log.getInstance()
        .write(
            Log.LOGLEVEL_TRACE,
            "sageEvent: Max to keep = " + (maxToKeep == DEFAULT_MAX ? "unlimited" : maxToKeep));

    // See how many are already recorded.
    int numberRecorded = Util.getNumberRecorded(MediaFile);
    Log.getInstance()
        .write(Log.LOGLEVEL_TRACE, "sageEvent: Number already recorded = " + numberRecorded);

    // If it's unlimited or below the threshhold don't worry about it.
    if (maxToKeep == UNLIMITED || numberRecorded <= maxToKeep) {
      Log.getInstance().write(Log.LOGLEVEL_TRACE, "sageEvent: Below threshhold.");
      return;
    }

    Log.getInstance()
        .write(
            Log.LOGLEVEL_TRACE,
            "sageEvent: Threshhold exceeded. Deleting one or more "
                + AiringAPI.GetAiringTitle(MediaFile));

    // Get the direction to sort.
    boolean keepOldest =
        Configuration.GetServerProperty(PROPERTY_KEEP_OLDEST, "true").equalsIgnoreCase("true");
    Log.getInstance().write(Log.LOGLEVEL_TRACE, "sageEvent: Keep oldest = " + keepOldest);

    // Get all of the recordings in the proper order. Recordings at the beginning of the
    // List will be deleted first.
    List<Object> allRecorded = Util.getAllRecorded(MediaFile, "GetAiringStartTime", keepOldest);

    Log.getInstance()
        .write(Log.LOGLEVEL_TRACE, "sageEvent: Sorted list size = " + allRecorded.size());

    if (Log.getInstance().GetLogLevel() <= Log.LOGLEVEL_VERBOSE) {
      for (Object MF : allRecorded)
        Log.getInstance()
            .write(
                Log.LOGLEVEL_VERBOSE,
                "sageEvent: Date recorded = "
                    + Utility.PrintDateLong(AiringAPI.GetAiringStartTime(MF))
                    + " : "
                    + Utility.PrintTimeLong(AiringAPI.GetAiringStartTime(MF))
                    + " - "
                    + AiringAPI.GetAiringTitle(MF)
                    + " - "
                    + ShowAPI.GetShowEpisode(MF));
    }

    boolean reduceToMax =
        Configuration.GetServerProperty(PROPERTY_REDUCE_TO_MAX, "false").equalsIgnoreCase("true");

    // Calculate how many to delete.
    int numberToDelete = (reduceToMax ? numberRecorded - maxToKeep : 1);

    Log.getInstance().write(Log.LOGLEVEL_TRACE, "sageEvent: Need to delete " + numberToDelete);

    // Sanity check.
    if (allRecorded == null || allRecorded.size() < numberToDelete || numberToDelete < 1) {
      Log.getInstance()
          .write(
              Log.LOGLEVEL_WARN,
              "sageEvent: Internal error. numberToDelete exceeds allRecorded. Deleting this MediaFile.");
      MediaFileAPI.DeleteFile(MediaFile);
      return;
    }

    for (int i = 0; i < numberToDelete; i++) {
      Object MF = allRecorded.get(i);

      // Log.getInstance().write(Log.LOGLEVEL_TRACE, "sageEvent: TESTMODE. Would have deleted " +
      // AiringAPI.GetAiringTitle(MF) + " - " + ShowAPI.GetShowEpisode(MF));
      if (MediaFileAPI.DeleteFile(MF))
        Log.getInstance()
            .write(
                Log.LOGLEVEL_TRACE,
                "sageEvent: Deleted "
                    + AiringAPI.GetAiringTitle(MF)
                    + " - "
                    + ShowAPI.GetShowEpisode(MF));
      else
        Log.getInstance()
            .write(
                Log.LOGLEVEL_WARN,
                "sageEvent: Failed to delete "
                    + AiringAPI.GetAiringTitle(MF)
                    + " - "
                    + ShowAPI.GetShowEpisode(MF));
    }
  }