Version 2.0 is finally out the door. Here are some of the new features:
Channel Rules
This is the big feature for this release. You're now able to customize every channel with a set of (currently) 12 different rules. Here is a quick rundown:
Scheduling - Want to watch The Daily Show every night at 7:00? Schedule your channel to play it.
Don't play this channel - Since some rules use other channels as modifiers (scheduling and interleaving), you may not want to actually play your channel that only has dozens of Battlestar Galactica episodes. Hide it with this rule.
Don't play a show on this channel - I want to watch my episodes of Alias in order, but they keep showing up on my ABC channel. Excluding them is only a rule away.
Force Random / Real-Time / Resume - While most people use Real-Time mode (as recommended), you can now have a channel be different from the rest.
Rename a channel - Don't like the default channel name? Rename it to "Chick Stuff" to show that only your wife wants to watch the crap that plays on this channel*.
Interleaving - Are you one of those people that likes bumpers? Then just create a channel that has only the bumpers and use this rule on your main channel to interleave those bumpers in between your shows.
Only play unwatched shows - If the channel contents change a lot, there's no reason to watch the same shows over and over. Hide things you've watched from this channel with ease.
Only play watched shows - Don't want to spoil a movie by accidentally seeing part of the middle? Hide shows that haven't been watched yet.
Play shows in order - Now you can make sure that when you're watching Top Chef, the next episode that comes on will actually be the next episode in the series.
Always Keep Paused - Don't want to miss an episode of The Big Bang Theory? Make sure that if you're not watching it, it stays paused. This rule goes great with the "Play shows in order" and "Force resume" rules!
* this is really the channel name I have for where my wife's crappy shows play.
Channel Sharing
I'm an old man, so my bed time is 10pm. If I'm in the middle of a show at 9:15 and want to go from downstairs to upstairs, I just turn off the TV and turn on my system in my bedroom. The channels are shared between the two so I can keep watching where I left off. Huzzah!
Setting this up is a simple matter of pointing each computer to the same directory in the addon settings:
Background Channel Loading
As someone without patience, I want everything to start as quickly as possible. Can I get rid of the loading time all together? No...sorry. I can cut it down, though. As long as at least one channel is ready to go, that channel will be started. Every other channel will be updated and added to the system as soon as they are ready. A little notification will be given to show that channels are loading:
And another is shown when a new channel is available:
Other Changes
There are some other new things going on, although they may not be as obvious. Here's a quick rundown:
Directory-type channels are supported now.
The EPG clock can be set to use a 12-hour or 24-hour clock.
Channel creation for first-time users has been significantly improved.
Time accuracy between restarts is essentially perfect now...or close enough for government work.
The EPG should function a bit quicker now.
I hope you enjoy the changes. There are still a couple of bugs to be worked out (IceLibrary doesn't work, yet) and a couple of features, but this version feels pretty solid to me.
As a side note, I've created a little Windows program (Vista and Windows 7 only) called the "Simple Range Compressor" that tries to maintain the same volume between shows by performing some dynamic range compression using the Windows volume slider...wacky, but it works:
Download it here, discuss it here.
Friday, October 28, 2011
Friday, September 2, 2011
Channel Scheduling
There is an update to stable-pre in the repository. This has the standard bug fixes as should be expected for every release. It also fixes (and slightly modifies) the interleave rule. It's now possible to select a minimum and maximum amount for the interleaving value. This means that the number of shows between each inserted episode will be chosen randomly between the minimum and maximum value. If you pick 3 and 5, for example, then there will be between 3 and 5 episodes of the normal channel followed by a single episode of the inserting channel. After that, 3 to 5 shows of the normal channel and 1 of the inserted.
It also has at least the initial version of the scheduling. I've decided to rename this to "Best-Effort" scheduling.
Best-Effort Channel Scheduling
Using this rule you do not schedule a movie or show, you schedule one channel to be played at a certain time during another channel. This gives all sorts of flexibility since the scheduled channel can use rules of its own.
Why is it best-effort instead of just real "play this show exactly at this time" scheduling? Because that way would be really damn hard, if not impossible.
When you watch a normal broadcast channel, the length of everything from the shows to commercials to the black spots in between are all timed so that the final length of an episode of the Simpsons will be 30 minutes. I don't have that control. In PseudoTV I give most of the control to XBMC itself to play a playlist that is created. The best that I can do is insert an item into that playlist that should play (based on the length of each show before it) at about the time you want.
Of course just inserting a show blindly at about that time would result in really shitty timing. If a movie is normally playing between 6 and 8 and you've scheduled something for 7, what would I do? Either way the show will be an hour off. Ideally what I would do is to rearrange the shows so that something ends at exactly 7 so that the new show can be inserted. As I discovered, this is classic computer science problem called the subset sum problem. Can any of the values in this subset be added together to get to zero. This is effectively the problem that I faced.
First attempt
My first goal was to solve this problem. In this case, I could setup a range that was valid: anywhere between 120 seconds before or after the time slot was good enough. I hoped that this would make it easier. I also decided to confine my search to only combinations of up to 4 values. So I created an array that had the sum of all values added together:
{ [ index 1: 1021, index 2: 1426, total: 2447 ], [ index 1: 1021, index 3: 709, total: 1730 ], ... , [ index 499: 639, index 500: 3285, total: 3924 ] }
On top of that, I appended all combinations of 3 values, and then all combinations of 4 values. It would be only a matter of taking the time that I wanted and seeing if swapping the shows from before the time to after would get me to the value that I wanted.
This took quite a bit of memory. For only 10 shows I would have a total of 45 combinations of two, 120 combinations of three, and 330 combinations of four. This means that for only 10 shows I would have 495 array entries. Now, computers have a lot of memory. In reality, these 495 entries would probably take up about 6KB worth of memory, not a significant amount by any means. As the total number of entries increases, though, the total amount of memory grows extremely quickly. Going from 10 to 20 entries raises the memory requirements to 88KB, 30 entries takes up 431KB.
Even this isn't a huge amount. The problem is that, on occasion, the size of a playlist will get into the thousands. How much memory? I honestly have no idea. I pushed my test program up to 300 entries and before it crashes it was taking up over 1GB worth of memory. Obviously, this approach isn't feasible.
It's worth mentioning that, yes, it could be optimized. I could sort the entries by time and remove duplicates. Since I can't swap the same show more than once I could eliminate duplicate indexes. Even so, this trades memory usage for processor usage. While I don't mind taking up some time, a script inside of XBMC should not be killing your computer...other things need to take precedence, like playing video (for instance).
Second attempt (the best-effort method)
So I still need to schedule shows, but having a script solve the subset sum problem for thousands of elements probably won't work. What I really need is some crappy method that will work, even if it isn't ideal.
I went back to the ridiculous array I created. With some experimentation, I realized that since the number of elements in the subset (playlist) actually allowed for a reasonable switch of two items to get my time correct. Essentially, I found that a combination of 2 items worked great in most situations. Excellent, I can create the combinations of 2 array and just look for the closest match out of that.
This simple solution solved most of the problems. There were situations that still didn't work properly, though. To resolve this, I just put my function inside of a nice little loop:
for loops in range(5):
newtime = self.rearrangeShows(showindex, lasttime, channeldata, channelList)
It also has at least the initial version of the scheduling. I've decided to rename this to "Best-Effort" scheduling.
Best-Effort Channel Scheduling
Using this rule you do not schedule a movie or show, you schedule one channel to be played at a certain time during another channel. This gives all sorts of flexibility since the scheduled channel can use rules of its own.
Why is it best-effort instead of just real "play this show exactly at this time" scheduling? Because that way would be really damn hard, if not impossible.
When you watch a normal broadcast channel, the length of everything from the shows to commercials to the black spots in between are all timed so that the final length of an episode of the Simpsons will be 30 minutes. I don't have that control. In PseudoTV I give most of the control to XBMC itself to play a playlist that is created. The best that I can do is insert an item into that playlist that should play (based on the length of each show before it) at about the time you want.
Of course just inserting a show blindly at about that time would result in really shitty timing. If a movie is normally playing between 6 and 8 and you've scheduled something for 7, what would I do? Either way the show will be an hour off. Ideally what I would do is to rearrange the shows so that something ends at exactly 7 so that the new show can be inserted. As I discovered, this is classic computer science problem called the subset sum problem. Can any of the values in this subset be added together to get to zero. This is effectively the problem that I faced.
First attempt
My first goal was to solve this problem. In this case, I could setup a range that was valid: anywhere between 120 seconds before or after the time slot was good enough. I hoped that this would make it easier. I also decided to confine my search to only combinations of up to 4 values. So I created an array that had the sum of all values added together:
{ [ index 1: 1021, index 2: 1426, total: 2447 ], [ index 1: 1021, index 3: 709, total: 1730 ], ... , [ index 499: 639, index 500: 3285, total: 3924 ] }
On top of that, I appended all combinations of 3 values, and then all combinations of 4 values. It would be only a matter of taking the time that I wanted and seeing if swapping the shows from before the time to after would get me to the value that I wanted.
This took quite a bit of memory. For only 10 shows I would have a total of 45 combinations of two, 120 combinations of three, and 330 combinations of four. This means that for only 10 shows I would have 495 array entries. Now, computers have a lot of memory. In reality, these 495 entries would probably take up about 6KB worth of memory, not a significant amount by any means. As the total number of entries increases, though, the total amount of memory grows extremely quickly. Going from 10 to 20 entries raises the memory requirements to 88KB, 30 entries takes up 431KB.
Even this isn't a huge amount. The problem is that, on occasion, the size of a playlist will get into the thousands. How much memory? I honestly have no idea. I pushed my test program up to 300 entries and before it crashes it was taking up over 1GB worth of memory. Obviously, this approach isn't feasible.
It's worth mentioning that, yes, it could be optimized. I could sort the entries by time and remove duplicates. Since I can't swap the same show more than once I could eliminate duplicate indexes. Even so, this trades memory usage for processor usage. While I don't mind taking up some time, a script inside of XBMC should not be killing your computer...other things need to take precedence, like playing video (for instance).
Second attempt (the best-effort method)
So I still need to schedule shows, but having a script solve the subset sum problem for thousands of elements probably won't work. What I really need is some crappy method that will work, even if it isn't ideal.
I went back to the ridiculous array I created. With some experimentation, I realized that since the number of elements in the subset (playlist) actually allowed for a reasonable switch of two items to get my time correct. Essentially, I found that a combination of 2 items worked great in most situations. Excellent, I can create the combinations of 2 array and just look for the closest match out of that.
This simple solution solved most of the problems. There were situations that still didn't work properly, though. To resolve this, I just put my function inside of a nice little loop:
for loops in range(5):
newtime = self.rearrangeShows(showindex, lasttime, channeldata, channelList)
if newtime == lasttime or newtime < 120:
break
lasttime = newtime
This gives the system 5 chances at fixing the time, by switching up to 5 pairs of shows. If the difference in the time between when I scheduled the show and when it will actually play is ever less than 120 seconds (plus or minus) then it will just say "good enough".
This method works pretty damn well, surprisingly. It isn't optimal. It does, though, minimize memory usage and attempts to get the optimal show placement out of as few processor cycles as possible.
Results
The outcome of this algorithm, though, is heavily dependent upon the channel contents itself. If you're scheduling a TV show on a channel that plays only movies, then it may be difficult for it to place the show very well. There would be fewer playlist entries and since the times for each entry is so high (2 hours or so, compared to 25 minutes of a TV show) then it may be difficult to place a show when you want it. Can I improve this algorithm? I'm sure it could be better. Will I? I'm not sure it's worth while, honestly. I think any results I get would only be incrementally better, and since the function is only a minor part of the PseudoTV package (and will probably rarely get used as it is) I don't think it's worth my time. Hopefully everyone will be happy with the current results, though.
If you do end up with large discrepancies between when you schedule a show and when it plays, please report it! While I probably won't change the algorithm much, I certainly will fix any bugs that show up.
Monday, August 8, 2011
File Locks
New Feature
First a brief description of a new feature upcoming in PseudoTV. I have 2 systems that run XBMC and PseudoTV in my house, one in my living room and one in my bedroom. There have been several times that I've wanted to stop watching something in the living room and continue it upstairs. That is now possible. There is a new option to specify the directory to store PseudoTV playlists and settings. If two systems share this directory, then they will both use the same set of channels and (with only minor differences) they will play them at the same times.
Implementation
This leads to the second part of this post: File locks over a network when using multiple platforms. What if I turn on TV 1 and it starts loading the channels. I then turn on TV 2 and it starts loading. If TV 1 modifies any of the playlists, TV 2 won't necessarily know and things will get screwed up. Who is allowed to extend the channel lists? Who is allowed to write to the settings? This is where file locks come into play.
Locking a file on a local hard drive so that other programs can't access it is generally possible, as long as you're writing for that platform specifically. It can even be done on networked drives, again, presuming that the systems are the same and you target them specifically. XBMC runs on multiple platforms so I can't presume anything of that sort. So how can I make sure that two instances don't start writing to the same file at the same time? How do I lock files?
The code is actually pretty simple...here are the steps:
1. Create a general lock file master list (I use FileLock.dat).
2. In order to lock a file, the first step is for a system to rename the master list to some random number filename (87385.lock). This is effectively the atomic operation needed for locking files.
3. In order to verify that it succeeded, it makes sure it can open the file for reading. If it can, then it now has control over the master file lock list.
4. Look in the list to see if the file you wish to lock is already there. If it is, then it's locked. If it isn't, then add it to the list.
5. Rename the list back to the original name.
That's it. The file is locked.
Issues
If only it where that simple. The problems arise when things don't work like they should. How does the system know that it needs to create the lock list, and not that it doesn't just need to wait for another system to rename it back? What happens if a system crashes and doesn't release a lock file, is that file permanently locked?
I'll start with the first question. If any given system can't get access the master list for some amount of time (10 seconds) then it needs to just create the list. Since, once a system has control over the list, the operations themselves take almost no time at all then the timeout value is very reasonable. Is this perfect? No. It will not scale very well. I would not recommend using this method for 10 machines at once since it may cause problems. For a small scale, though? Great.
What about the second issue, where a file can become permanently locked? This is resolved by adding a random value to the same line as the locked file in the list. For example:
918374, MyLockedFile.txt
Every few seconds, the owner of that file will re-lock the file with a new random number. Whoever wants access to MyLockedFile.txt will just watch the list and make sure that number actually changes. If it doesn't change for some amount of time (10 seconds) then it assumes that the lock is dead, and it rewrites it. If it does change then the lock is valid.
There's nothing terribly difficult about this method, but it took me a while to figure out...hopefully others will benefit from this. The python code where I use this is here.
First a brief description of a new feature upcoming in PseudoTV. I have 2 systems that run XBMC and PseudoTV in my house, one in my living room and one in my bedroom. There have been several times that I've wanted to stop watching something in the living room and continue it upstairs. That is now possible. There is a new option to specify the directory to store PseudoTV playlists and settings. If two systems share this directory, then they will both use the same set of channels and (with only minor differences) they will play them at the same times.
Implementation
This leads to the second part of this post: File locks over a network when using multiple platforms. What if I turn on TV 1 and it starts loading the channels. I then turn on TV 2 and it starts loading. If TV 1 modifies any of the playlists, TV 2 won't necessarily know and things will get screwed up. Who is allowed to extend the channel lists? Who is allowed to write to the settings? This is where file locks come into play.
Locking a file on a local hard drive so that other programs can't access it is generally possible, as long as you're writing for that platform specifically. It can even be done on networked drives, again, presuming that the systems are the same and you target them specifically. XBMC runs on multiple platforms so I can't presume anything of that sort. So how can I make sure that two instances don't start writing to the same file at the same time? How do I lock files?
The code is actually pretty simple...here are the steps:
1. Create a general lock file master list (I use FileLock.dat).
2. In order to lock a file, the first step is for a system to rename the master list to some random number filename (87385.lock). This is effectively the atomic operation needed for locking files.
3. In order to verify that it succeeded, it makes sure it can open the file for reading. If it can, then it now has control over the master file lock list.
4. Look in the list to see if the file you wish to lock is already there. If it is, then it's locked. If it isn't, then add it to the list.
5. Rename the list back to the original name.
That's it. The file is locked.
Issues
If only it where that simple. The problems arise when things don't work like they should. How does the system know that it needs to create the lock list, and not that it doesn't just need to wait for another system to rename it back? What happens if a system crashes and doesn't release a lock file, is that file permanently locked?
I'll start with the first question. If any given system can't get access the master list for some amount of time (10 seconds) then it needs to just create the list. Since, once a system has control over the list, the operations themselves take almost no time at all then the timeout value is very reasonable. Is this perfect? No. It will not scale very well. I would not recommend using this method for 10 machines at once since it may cause problems. For a small scale, though? Great.
What about the second issue, where a file can become permanently locked? This is resolved by adding a random value to the same line as the locked file in the list. For example:
918374, MyLockedFile.txt
Every few seconds, the owner of that file will re-lock the file with a new random number. Whoever wants access to MyLockedFile.txt will just watch the list and make sure that number actually changes. If it doesn't change for some amount of time (10 seconds) then it assumes that the lock is dead, and it rewrites it. If it does change then the lock is valid.
There's nothing terribly difficult about this method, but it took me a while to figure out...hopefully others will benefit from this. The python code where I use this is here.
Subscribe to:
Posts (Atom)