|
SageTV v7 Customizations This forums is for discussing and sharing user-created modifications for the SageTV version 7 application created by using the SageTV Studio or through the use of external plugins. Use this forum to discuss plugins for SageTV version 7 and newer. |
|
Thread Tools | Search this Thread | Display Modes |
#661
|
|||
|
|||
Quote:
Quote:
ID JOB_ID CREATED ASSIGNED FINISHED STATE HOST PORT SHOW_ICON 14191 COMSKIPTIMER 2011-04-03 04:37:00.0 2011-04-05 18:42:51.684 2011-04-05 18:42:55.325 RETURNED whs 23344 FALSE The client setup from sjqagent.properties is: Code:
#Generated by SJQv4 agent #Tue Mar 22 19:23:20 CDT 2011 task.comskip1.schedule=ON task.comskip1.args="$SJQ4_PATH\\$SJQ4_LAST_SEGMENT" agent.port=23344 task.comskip1.resources=50 task.comskip2.schedule=ON task.comskip2.maxprocs=2 task.comskip1.maxtime=28800 task.comskip1.testargs=mpg task.comskiptimer.exe=c\:/comskip/comskip.exe task.comskiptimer.rcmin=0 task.comskip2.maxtimeratio=1.0 task.comskip1.test=c\:/sjqagent/comskip_test.groovy task.comskiptimer.args= task.comskip2.exe=c\:/comskip/comskip.exe task.comskip2.testargs=ts task.comskip2.rcmax=2 task.comskip1.rcmax=2 task.comskiptimer.maxtimeratio=1.0 task.comskip2.args="$SJQ4_PATH\\$SJQ4_LAST_SEGMENT" agent.schedule=ON task.comskip1.maxtimeratio=1.0 task.comskiptimer.schedule=ON task.comskiptimer.maxprocs=1 task.comskip2.resources=50 task.comskip2.maxtime=86400 task.comskiptimer.maxtime=86400 task.comskip1.exe=c\:/comskip/comskip.exe task.comskiptimer.resources=0 task.comskiptimer.test=c\:/sjqagent/comskip_timer_test.groovy task.comskiptimer.rcmax=1 task.comskiptimer.testargs= task.comskip2.test=c\:/sjqagent/comskip_test.groovy agent.mapdir= agent.resources=100 task.comskip2.rcmin=0 task.comskip1.rcmin=0 task.comskip1.maxprocs=2 Last edited by K O; 04-05-2011 at 06:10 PM. |
#662
|
|||
|
|||
Bingo!!!
There should be no test script configured for the timer task... it just runs periodically. Remove the test script for the COMSKIPTIMER task. You will have to purge the current queue (I think, maybe not, but just do it). That should fix all your problems (as far as tasks constantly rerunning).
__________________
Twitter: @ddb_db Server: Intel i5-4570 Quad Core, 16GB RAM, 1 x 128GB OS SSD (Win7 Pro x64 SP1), 1 x 2TB media drive Capture: 2 x Colossus STB Controller: 1 x USB-UIRT Software:Java 1.7.0_71; SageTV 7.1.9 Clients: 1 x HD300, 2 x HD200, 1 x SageClient, 1 x PlaceShifter Plugins: Too many to list now... |
#663
|
|||
|
|||
Quote:
Have a look at what the test script does - is there a better way of doing this without the test script? It scans the recordings for items that don't have .edl files and runs the other comskip task on them. Code:
/* Media File Scanner/Task Queuer Last Modified: 15 Feb 2011 Author: Derek Battams <derek AT battams DOT ca> Use this script to periodically scan your media objects and check to see if any need to have tasks queued on them. Basically, set up your media mask and then modify the needsProcessing() function to do the checks you want against each object. If needsProcessing() returns true for an object then it will queue up each task listed in the taskIds list for that object. Typically, you would run this script periodically via the SJQv4 crontab. PLEASE RUN THIS SCRIPT WITH testMode = true BEFORE ALLOWING IT TO ACTUALLY QUEUE UP TASKS!! */ /***** CONFIGURE BELOW *****/ def testMode = false // If true, only print out which media files would be queued up, don't actually add the tasks to the queue def mediaMask = "T" // What types of media should be scanned? (T = TV, M = Music, V = Imported Video, D = DVD, B = BluRay, P = Pictures; TMV = TV + Music + Imports, etc.) def taskIds = ["COMSKIP1", "COMSKIP2"] // Multiple tasks can be listed, separated by commas /* Returns true if the argument needs to be queued or false if it should be skipped Modify this function to determine which media files get queued up and which don't */ def needsProcessing(Object mediaFile) { // This function could be written in a much more condensed manner, but I'm breaking it up for the sake of readability // So let's skip queuing this media file if it's live tv or an IR recording if(AiringAPI.IsNotManualOrFavorite(mediaFile)) return false // Personally, I don't comskip until the recording is done, so don't queue up recordings in progress if(MediaFileAPI.IsFileCurrentlyRecording(mediaFile)) return false // Let's also skip it if it's from a channel known not to have commercials (adjust the regex accordingly) //if(AiringAPI.GetAiringChannelName(mediaFile) =~ /HBO.*|M(?:HD){0,1}|WPBS|.*PPV.*/) //return false // Let's also skip it if there is already an edl file for the media file; adjust the extensions, if necessary if(hasArtifacts(mediaFile, ["edl"])) // This function is defined at the bottom of the file return false // All our tests have passed so return true return true } /***** END CONFIG BLOCK *****/ /***** DO NOT MODIFY BELOW THIS LINE *****/ import com.google.code.sagetvaddons.sjq.network.ServerClient import com.google.code.sagetvaddons.metadata.Factory import org.apache.commons.io.FilenameUtils def sc = !testMode ? new ServerClient() : null MediaFileAPI.GetMediaFiles(mediaMask).each { mf -> if(needsProcessing(mf)) { if(!testMode) taskIds.each { id -> sc.addTask(id, Factory.getMap(mf)) } else println "Would queue up '${MediaFileAPI.GetMediaTitle(mf)}' (${MediaFileAPI.GetMediaFileID(mf)}); skipped because test mode is TRUE" } } if(sc != null) sc.close() return 1 // Returns true if any segment of the given media file has at least one artifact of any of the given artifact extensions; false otherwise def hasArtifacts(Object mf, List exts) { for(def it : MediaFileAPI.GetSegmentFiles(mf)) { def absPath = it.getAbsolutePath() def dir = FilenameUtils.getFullPath(absPath) def base = FilenameUtils.getBaseName(absPath) for(def ext : exts) { def artifact = "${dir}${base}.$ext" if(Utility.IsFilePath(artifact)) return true } } return false } |
#664
|
|||
|
|||
The exe for that task should be what you are currently using as the test. That script should then be queuing COMSKIP tasks, which calls comskip.exe. The scanner should never be calling comskip.exe directly. The media scan script was not designed to be a test script, but rather an exe script.
In your sjqagent.properties: Code:
task.comskiptimer.exe=c\:/comskip/comskip.exe task.comskiptimer.test=c\:/sjqagent/comskip_timer_test.groovy Code:
task.comskiptimer.test= task.comskiptimer.exe=script\:c\:/sjqagent/comskip_timer_test.groovy
__________________
Twitter: @ddb_db Server: Intel i5-4570 Quad Core, 16GB RAM, 1 x 128GB OS SSD (Win7 Pro x64 SP1), 1 x 2TB media drive Capture: 2 x Colossus STB Controller: 1 x USB-UIRT Software:Java 1.7.0_71; SageTV 7.1.9 Clients: 1 x HD300, 2 x HD200, 1 x SageClient, 1 x PlaceShifter Plugins: Too many to list now... |
#665
|
|||
|
|||
That makes sense now that you point it out. I don't know that I could have ever figured that out. Thanks for your help!
|
#666
|
|||
|
|||
Would there be the possibility of showing the show title in the job details without having to dig down into the Show metadata? This would make it so much easier to find the log for a particular show
|
#667
|
|||
|
|||
Quote:
__________________
Twitter: @ddb_db Server: Intel i5-4570 Quad Core, 16GB RAM, 1 x 128GB OS SSD (Win7 Pro x64 SP1), 1 x 2TB media drive Capture: 2 x Colossus STB Controller: 1 x USB-UIRT Software:Java 1.7.0_71; SageTV 7.1.9 Clients: 1 x HD300, 2 x HD200, 1 x SageClient, 1 x PlaceShifter Plugins: Too many to list now... |
#668
|
||||
|
||||
In trying to decide whether it's safe to move the file(s) over, I've been looking for some SageAPI that would tell me if a show is currently being watched but am having no luck with a check of 'system' status like that. I see a few things that might be interesting under MediaPlayerAPI, but not if a show is paused, or otherwise locked. Best I can think of is to do a copy rather than move, attempt a delete and if it fails put the delete in a new task to try later. Any suggestion on a way to test for a file open/locked by sage or better way to handle the situation?
|
#669
|
||||
|
||||
In the MediaPlayerAPI there is a method (might be GetCurrentMediaFile() but I'm not sure since I'm going from memory) that will tell you what MediaFile the player has loaded. "Loaded" just means it's being watched, and may or may not be paused. I think that's the API you want. Just compare the MediaFileID of the loaded MediaFile with the MediaFile you are interested in to check for equality.
Tom
__________________
Sage Server: 8th gen Intel based system w/32GB RAM running Ubuntu Linux, HDHomeRun Prime with cable card for recording. Runs headless. Accessed via RD when necessary. Four HD-300 Extenders. |
#670
|
||||
|
||||
Quote:
|
#671
|
|||
|
|||
You've got to query what each player is doing. Loop through all UI contexts and connected clients. Look at each one's media player and query what it's playing, if anything. If it's playing something then see if it's playing the object in question and respond accordingly.
__________________
Twitter: @ddb_db Server: Intel i5-4570 Quad Core, 16GB RAM, 1 x 128GB OS SSD (Win7 Pro x64 SP1), 1 x 2TB media drive Capture: 2 x Colossus STB Controller: 1 x USB-UIRT Software:Java 1.7.0_71; SageTV 7.1.9 Clients: 1 x HD300, 2 x HD200, 1 x SageClient, 1 x PlaceShifter Plugins: Too many to list now... |
#672
|
||||
|
||||
Quote:
Yeah,... I'm afraid I've just gone way, way down the road of "Huh?"! While I get the concept, it's laughable how far over my head those three sentences are.... Leaves me with the blind attempt at the move and if that fails, do a copy so I can proceed with the rest of my process on the desired naming and drop a delete in the task list for the original file(s) when it's done to try and clean up the self inflicted mess.... When I'm beat, I'm beat. |
#673
|
||||
|
||||
Quote:
__________________
Sage Server: 8th gen Intel based system w/32GB RAM running Ubuntu Linux, HDHomeRun Prime with cable card for recording. Runs headless. Accessed via RD when necessary. Four HD-300 Extenders. |
#674
|
|||
|
|||
I'm trying to encourage you get out there on your own and experiment. The concepts might seem difficult, but just break it down...
1) Find all the connected clients/extenders... Code:
def connected = [] connected += Arrays.asList(Global.GetUIContextNames()) connected += Arrays.asList(Global.GetConnectedClients()) Code:
def active = [] for(def c : connected) { def nowPlaying = MediaPlayerAPI.GetCurrentMediaFile(new UIContext(c)) if(nowPlaying != null) active.add(nowPlaying) } Code:
def mf // This is what we're testing against, fill it in someway, some how for(def playing : active) { if(MediaFileAPI.GetMediaFileID(mf) == MediaFileAPI.GetMediaFileID(playing)) { println "Someone's playing this media file!" } } Code:
import sagex.UIContext def mf = MediaFileAPI.GetMediaFileForID(6325006) // This is what we're testing against, fill it in someway, some how def connected = [] connected += Arrays.asList(Global.GetUIContextNames()) connected += Arrays.asList(Global.GetConnectedClients()) println "Connected: $connected" def active = [] for(def c : connected) { def nowPlaying = MediaPlayerAPI.GetCurrentMediaFile(new UIContext(c)) if(nowPlaying != null) { active.add(nowPlaying) println "Client '$c' is playing '${MediaFileAPI.GetMediaTitle(nowPlaying)}/${MediaFileAPI.GetMediaFileID(nowPlaying)}'" } } println "Checking if someone is playing the media file in question (def mf)..." for(def playing : active) { if(MediaFileAPI.GetMediaFileID(mf) == MediaFileAPI.GetMediaFileID(playing)) { println "Someone's playing this media file!" } } Code:
Connected: [001d6a5ca9d7] Client '001d6a5ca9d7' is playing 'The Daily Show With Jon Stewart/6331951' Checking if someone is playing the media file in question (def mf)... Also, functions like this I put in a library of Groovy scripts, not yet released, but probably eventually. It's a collection of commonly used functions and queries and this is one of them that I'll be adding when I get a chance. So eventually you'll be able to pull in my jar file and do this: if(SluggersTools.isSomeoneWatching(mf)) { // Someone's watching it, handle it } else { // No one's watching it, do your thing } But realize that under the hood, that isSomeoneWatching() function is basically doing some variation of the code I wrote above. Once I get enough content in my library and I properly document it, I'll release it. In the meantime, you'll need something like the example I've provided to do what you want to do.
__________________
Twitter: @ddb_db Server: Intel i5-4570 Quad Core, 16GB RAM, 1 x 128GB OS SSD (Win7 Pro x64 SP1), 1 x 2TB media drive Capture: 2 x Colossus STB Controller: 1 x USB-UIRT Software:Java 1.7.0_71; SageTV 7.1.9 Clients: 1 x HD300, 2 x HD200, 1 x SageClient, 1 x PlaceShifter Plugins: Too many to list now... |
#675
|
||||
|
||||
I appreciate the help and encouragement, I really do. And I respect the 'teach a man to fish' concepts too. To assume there's no attempting and experimenting going on would be a mistake, there is, but a programmer I'm not so I don't get far (or at times, anywhere).... Regardless, message received. Didn't intend to hijack the sjq4 thread with programmer 101 questions, apologies.
|
#676
|
|||
|
|||
Quote:
What's better is to say, "I'm trying to find out if the media file is currently being viewed, but I'm stuck. Here's what I've tried: <insert your code in its current state, etc.>, but for some reason I just don't see it. Any suggestions?" The latter, with your example code, shows the effort and saves me, others work in coming up with examples. I'd rather look at your effort and tweak it as necessary than to write full blown examples for everything. You're also more likely to understand if I tweak your code here and there instead of giving you 40 lines from scratch. The former reads as if you have the code as last given to you and now you want to add something else to it so you're asking for more "help" in doing that. I don't mind helping, I really don't, but the first few examples are "free", after awhile I'd expect to see your code and then help you tweak it as necessary instead of producing full blown examples. So keep working away at it, and keep asking questions, but don't be shy about showing your work.
__________________
Twitter: @ddb_db Server: Intel i5-4570 Quad Core, 16GB RAM, 1 x 128GB OS SSD (Win7 Pro x64 SP1), 1 x 2TB media drive Capture: 2 x Colossus STB Controller: 1 x USB-UIRT Software:Java 1.7.0_71; SageTV 7.1.9 Clients: 1 x HD300, 2 x HD200, 1 x SageClient, 1 x PlaceShifter Plugins: Too many to list now... |
#677
|
|||
|
|||
I decided to go ahead and put my tools jar up on the project site for anyone to use.
Download gtools-n.n.n.n.zip from here. The groovydocs (i.e. javadocs) for the library are updated here. To install, copy the jar file into the lib directory of your standalone task client. If you're using SageGroovy you must also install it in the lib folder of it as well. If you're using the Sage plugin task client you must install the jar into SageTV\JARs and restart SageTV. Eventually I'll provide a plugin for SageTV, but that's not planned for anytime soon. To use it, just import the class(es) you want to use in your scripts and then have at it! With this jar, checking if a media file is currently being viewed somewhere is as easy as this: Code:
import com.google.code.sagetvaddons.groovy.api.* def mf = MediaFileAPI.GetMediaFileForID(123456) // This probably doesn't exist! if(MediaFileHelpers.isBeingViewed(mf)) { // Being viewed somewhere, do whatever } else { // Not being viewed anywhere, do whatever } Happy scripting!
__________________
Twitter: @ddb_db Server: Intel i5-4570 Quad Core, 16GB RAM, 1 x 128GB OS SSD (Win7 Pro x64 SP1), 1 x 2TB media drive Capture: 2 x Colossus STB Controller: 1 x USB-UIRT Software:Java 1.7.0_71; SageTV 7.1.9 Clients: 1 x HD300, 2 x HD200, 1 x SageClient, 1 x PlaceShifter Plugins: Too many to list now... |
#678
|
||||
|
||||
Quote:
But if that's the way you like it, here goes: I've got the test script setup to check for both a recording in progress and a watching in progress, i.e. tied up and untouchable. Lack of metadata and log in the active queue listing is the issue. The task looks like this: definition in sage is: executable -Script:\\Sage-pc\c\Program Files (x86)\SageTV\scripts\rename_comskip.groovy executalbe args - "$SJQ4_PATH\$SJQ4_LAST_SEGMENT" test script is - \\Sage-pc\c\Program Files (x86)\SageTV\scripts\safe_to_rename_test.groovy test scripts args - none Test script looks like this: Code:
import sagex.UIContext def mf = MediaFileAPI.GetMediaFileForID(SJQ4_METADATA["SJQ4_ID"].toInteger()) // def mf = MediaFileAPI.GetMediaFileForID(4548538) // This is what we're testing against, fill it in someway, some how if(MediaFileAPI.IsFileCurrentlyRecording(mf)) { // If currently recording wait! println("Recording in progress, waiting..."); return 1; } def connected = [] connected += Arrays.asList(Global.GetUIContextNames()) connected += Arrays.asList(Global.GetConnectedClients()) println "Connected: $connected" def active = [] for(def c : connected) { def nowPlaying = MediaPlayerAPI.GetCurrentMediaFile(new UIContext(c)) if(nowPlaying != null) { active.add(nowPlaying) println "Client '$c' is playing '${MediaFileAPI.GetMediaTitle(nowPlaying)}/${MediaFileAPI.GetMediaFileID(nowPlaying)}'" } } println "Checking if someone is playing the media file in question (def mf)..." for(def playing : active) { if(MediaFileAPI.GetMediaFileID(mf) == MediaFileAPI.GetMediaFileID(playing)) { println "Someone's playing this media file!" return 1 } } println "File is clear and ready!" return 0 The test script seems to be running fine and making proper determinations, but when 'released' to the executable, the metadata isn't available and the log is empty, though the file rename happens and comskip does in fact run. Seems that the missing metadata (within the running task window) is a symptom of something not being right with my process. Since this symptom doesn't show itself when the executable is run without the test script I assume it's how they're interacting, not the executable code itself (but included here in case it is relevant). Code:
// def SJQ4_METADATA = ["SJQ4_ID":"4548571", "SJQ4_TYPE":"MediaFile"] // And then do whatever you were doing as normal; just remember to // remove the above line before using the scirpt in SJQv4. import org.apache.commons.io.FilenameUtils import org.apache.commons.io.FileUtils private class Settings { static public final boolean TEST_MODE = false } def mf = MediaFileAPI.GetMediaFileForID(SJQ4_METADATA["SJQ4_ID"].toInteger()) if(mf == null) { //make sure it's a valid file id println "Invalid media file id! [${SJQ4_METADATA['SJQ4_ID']}]" return 1 } AiringAPI.SetWatched(mf); // Lets set the show as watched before we go further // def cats = ShowAPI.GetShowCatagoriesList(mf) // show catagogries used to decide if its a move or tv series def type = MediaFileAPI.GetMediaFileMetadata(mf, "MediaType") def title = ShowAPI.GetShowTitle(mf) def sNum = ShowAPI.GetShowSeasonNumber(mf) def eNum = ShowAPI.GetShowEpisodeNumber(mf) def seNum = sNum > 0 && eNum > 0 ? String.format("S%02dE%02d", sNum, eNum) : null def subtitle = ShowAPI.GetShowEpisode(mf) def origAirDate = ShowAPI.GetOriginalAiringDate(mf) > 0 ? new Date(ShowAPI.GetOriginalAiringDate(mf)).format("yyyy-MM-dd") : null def year = ShowAPI.GetShowYear(mf) // if (cats.contains("Movie") || cats.contains("Film")) { if (type == "Movie") { def newPrefix = title + " /(/" + year + "/)/" } else { def newPrefix = "${title} - " if(seNum != null) newPrefix += seNum else if(subtitle != "") newPrefix += subtitle else if(origAirDate != null) newPrefix += origAirDate else newPrefix += "UNKNOWN" def file = new File(newPrefix + ".ts") def dir = file.getParent() def base = FilenameUtils.getBaseName(file.getName()) def ext = FilenameUtils.getExtension(file.getName()) def i = 1 while(file.exists()) file = new File(dir, "$base-${i++}.$ext") println "New file is: $file" newPrefix = FilenameUtils.getBaseName(file.getName()) def numSegments = MediaFileAPI.GetNumberOfSegments(mf) if(numSegments == 1) { def prefix = FilenameUtils.getBaseName(MediaFileAPI.GetFileForSegment(mf, 0).getAbsolutePath()) println "Renaming files that look like '${prefix}.*' to '${newPrefix}.*'..." renameMatches(MediaFileAPI.GetParentDirectory(mf), prefix, null, newPrefix) } else if(numSegments > 1) { for(def i2 = 0; i < numSegments; ++i2) { def prefix = FilenameUtils.getBaseName(MediaFileAPI.GetFileForSegment(mf, i2).getAbsolutePath()) def segPrefix = "$newPrefix-$i2" println "Renaming files that look like '${prefix}.*' to '${segPrefix}.*'..." renameMatches(MediaFileAPI.GetParentDirecotry(mf), prefix, null, segPrefix) } } else { println "No file segments for given media file!" return 1 } return 0 } // Pass null for newDir to keep renamed file in same directory def renameMatches(def oldDir, def prefix, def newDir, def newPrefix) { if(newDir == null) newDir = oldDir Utility.DirectoryListing(oldDir).each { if(FilenameUtils.wildcardMatchOnSystem(it.getName(), "${prefix}.*")) { def newName = (newPrefix + it.getName().substring(it.getName().indexOf('.')) =~ /[\/\\:*?<>]/).replaceAll("") if(!Settings.TEST_MODE) { try { FileUtils.moveFile(it, new File(newDir, newName)) } catch(IOException e) { e.printStackTrace() println("Failed to move file to destination!") FileUtils.copyFile(it, new File(newDir, newName)) } } else { println "Would rename '$it' to '$newName' if test mode were disabled!" } } } // comskip attempt def newName2 = (newPrefix =~ /[\/\\:*?<>]/).replaceAll("") // remove special characthers from newPrefix def fullpathfile = newDir.toString() + "/" + newName2 + ".ts" // Convert newDir from file type to string variable and add .ts to filename, combine. def command = ["c:/program files/comskip/comskip.exe", fullpathfile] //define command as an array println "full command will be '$command'" def proc = command.execute() if(!Settings.TEST_MODE) { // Are we running test mode, if not do this def initialSize = 4096 def outStream = new ByteArrayOutputStream(initialSize) def errStream = new ByteArrayOutputStream(initialSize) proc.consumeProcessOutput(outStream, errStream) proc.waitFor() println 'out:\n' + outStream println 'err:\n' + errStream } else { // if we are in test mode do this println "Would run '$command' if test mode were disabled!" } } return 0; |
#679
|
|||
|
|||
Quote:
__________________
Twitter: @ddb_db Server: Intel i5-4570 Quad Core, 16GB RAM, 1 x 128GB OS SSD (Win7 Pro x64 SP1), 1 x 2TB media drive Capture: 2 x Colossus STB Controller: 1 x USB-UIRT Software:Java 1.7.0_71; SageTV 7.1.9 Clients: 1 x HD300, 2 x HD200, 1 x SageClient, 1 x PlaceShifter Plugins: Too many to list now... |
#680
|
||||
|
||||
While the script is "running" I go into the UI under the queued tasks and select the metadata it tells me no metadata is available, when I select the log info it says no log info for this task, when I select ok, the task list goes blank and says "Loading.... if I exit out and come back in it shows the task(s) again.
Same behavior in completed tasks, though for that (different file being processed) task, metadata is there, log info isn't. The scripts do output what's expected, rename is working, comskip is running. Right now on queued tasks I've got one task running that's got no metadata, no log, but it's gotten to the comskip part of the script so there should be some println output at least. Next task is waiting, has no metadata and no log. On the completed side I've got two tasks showing successful, with metadata and no logs, when looking at metadata it's there and exits properly, with logs there's no data and closing that screen I get the Loading... till I exit and return. Is that clearer? I've always had some log info and metadata unless something was 'wrong' with the script. Then there's the wierd behavior of no log data clearing the listing till exit/return. |
Currently Active Users Viewing This Thread: 1 (0 members and 1 guests) | |
|
|
Similar Threads | ||||
Thread | Thread Starter | Forum | Replies | Last Post |
Plugin: MizookLCD (Alternate SageTV LCDSmartie Plugin) | cslatt | SageTV Customizations | 48 | 06-11-2012 10:44 AM |
SJQv4: Technology Preview | Slugger | SageTV v7 Customizations | 39 | 12-17-2010 01:17 PM |
SageTV Plugin Developers: Any way to see stats for your plugin? | mkanet | SageTV Software | 4 | 12-12-2010 10:33 PM |
MediaPlayer Plugin/STV Import: Winamp Media Player Plugin | deria | SageTV Customizations | 447 | 12-11-2010 07:38 PM |
SJQv4: Design Discussion | Slugger | SageTV v7 Customizations | 26 | 10-18-2010 08:22 AM |